From aaf4c6e54e6645f54043bcbfee7f544dfd5146e0 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 15:47:55 +0530
Subject: [PATCH 01/41] Switch SDK generation to Ferrule
Ferrule-Generated-From: c3b2fbd018dcc574a2c7392b3aec228d44de3a1c
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
.github/workflows/ci.yml | 156 +-
.github/workflows/publish-pypi.yml | 31 +
.github/workflows/release-doctor.yml | 21 +
.github/workflows/sdk.yml | 31 +
AGENTS.md | 9 +
api.md | 1253 +-
examples/agent/agent_list.py | 8 +
examples/agent/command_list.py | 8 +
examples/agent/config_get.py | 8 +
examples/agent/credential_list.py | 8 +
examples/agent/debug_location_list.py | 8 +
.../experimental_persistent_pty_handoff.py | 8 +
examples/agent/file_list.py | 8 +
examples/agent/form_list.py | 8 +
examples/agent/generate_text.py | 8 +
examples/agent/integration_command_cancel.py | 8 +
.../agent/integration_connect_external.py | 8 +
examples/agent/integration_list.py | 8 +
examples/agent/integration_oauth_cancel.py | 8 +
examples/agent/integration_wellknown_add.py | 8 +
examples/agent/location_get.py | 8 +
examples/agent/mcp_list.py | 8 +
examples/agent/mcp_resource_catalog.py | 8 +
examples/agent/migration_v1_status.py | 8 +
examples/agent/model_list.py | 8 +
examples/agent/permission_create.py | 8 +
examples/agent/permission_request_list.py | 8 +
examples/agent/permission_saved_list.py | 8 +
examples/agent/plugin_list.py | 8 +
examples/agent/project_list.py | 8 +
examples/agent/provider_list.py | 8 +
examples/agent/pty_connect_token.py | 8 +
examples/agent/pty_create.py | 8 +
examples/agent/reference_list.py | 8 +
examples/agent/rpc_call.py | 8 +
examples/agent/server_info.py | 8 +
examples/agent/session_create.py | 8 +
examples/agent/session_form_create.py | 8 +
examples/agent/session_inbox_update.py | 8 +
.../agent/session_instructions_entry_list.py | 8 +
examples/agent/session_message_get.py | 8 +
examples/agent/session_revert_clear.py | 8 +
examples/agent/shell_list.py | 8 +
examples/agent/skill_list.py | 8 +
examples/agent/vcs_base.py | 8 +
examples/agent/vcs_branch_list.py | 8 +
examples/agent/voice_session.py | 8 +
examples/agent/websearch_providers.py | 8 +
examples/agent/worktree_create.py | 8 +
ferrule.lock | 785 +
llms.txt | 9 +
openapi.json | 29772 ++++++++++++++++
scripts/utils/ruffen-docs.py | 167 +
skills/opencode/SKILL.md | 575 +
src/opencode/__init__.py | 289 +-
src/opencode/_agent/AGENTS.md | 9 +
src/opencode/_agent/SKILL.md | 575 +
src/opencode/_agent/llms.txt | 9 +
src/opencode/_base_client.py | 2542 +-
src/opencode/_client.py | 1804 +-
src/opencode/_compat.py | 226 +
src/opencode/_constants.py | 27 +-
src/opencode/_exceptions.py | 239 +-
src/opencode/_files.py | 173 +
src/opencode/_models.py | 968 +-
src/opencode/_qs.py | 240 +-
src/opencode/_resource.py | 43 +-
src/opencode/_response.py | 903 +-
src/opencode/_streaming.py | 480 +-
src/opencode/_types.py | 288 +-
src/opencode/_utils/__init__.py | 64 +
src/opencode/_utils/_compat.py | 45 +
src/opencode/_utils/_datetime_parse.py | 136 +
src/opencode/_utils/_json.py | 35 +
src/opencode/_utils/_logs.py | 25 +
src/opencode/_utils/_path.py | 127 +
src/opencode/_utils/_proxy.py | 65 +
src/opencode/_utils/_reflection.py | 42 +
src/opencode/_utils/_resources_proxy.py | 24 +
src/opencode/_utils/_streams.py | 12 +
src/opencode/_utils/_sync.py | 58 +
src/opencode/_utils/_transform.py | 457 +
src/opencode/_utils/_typing.py | 156 +
src/opencode/_utils/_utils.py | 433 +
src/opencode/pagination.py | 61 +
src/opencode/resources/__init__.py | 803 +-
src/opencode/resources/agent.py | 325 +-
src/opencode/resources/command.py | 198 +-
src/opencode/resources/config.py | 407 +-
src/opencode/resources/credential.py | 646 +-
src/opencode/resources/debug/__init__.py | 33 +
src/opencode/resources/debug/debug.py | 105 +
src/opencode/resources/debug/location.py | 225 +
src/opencode/resources/event.py | 173 +-
.../resources/experimental/__init__.py | 33 +
.../resources/experimental/experimental.py | 105 +
.../resources/experimental/persistent_pty.py | 881 +
src/opencode/resources/file.py | 650 +-
src/opencode/resources/form.py | 198 +-
src/opencode/resources/generate.py | 217 +-
.../resources/integration/__init__.py | 75 +
src/opencode/resources/integration/command.py | 405 +
src/opencode/resources/integration/connect.py | 319 +
.../resources/integration/integration.py | 391 +
src/opencode/resources/integration/oauth.py | 518 +
.../resources/integration/wellknown.py | 176 +
src/opencode/resources/location.py | 300 +-
src/opencode/resources/mcp/__init__.py | 33 +
src/opencode/resources/mcp/mcp.py | 571 +
src/opencode/resources/mcp/resource.py | 173 +
src/opencode/resources/message.py | 265 +-
src/opencode/resources/migration/__init__.py | 33 +
src/opencode/resources/migration/migration.py | 105 +
src/opencode/resources/migration/v1.py | 135 +
src/opencode/resources/model.py | 330 +-
src/opencode/resources/permission/__init__.py | 47 +
.../resources/permission/permission.py | 552 +
src/opencode/resources/permission/request.py | 171 +
src/opencode/resources/permission/saved.py | 352 +
src/opencode/resources/plugin.py | 451 +-
src/opencode/resources/project.py | 380 +-
src/opencode/resources/provider.py | 347 +-
src/opencode/resources/pty/__init__.py | 33 +
src/opencode/resources/pty/connect.py | 192 +
src/opencode/resources/pty/pty.py | 605 +
src/opencode/resources/reference.py | 198 +-
src/opencode/resources/rpc.py | 223 +-
src/opencode/resources/server.py | 375 +-
src/opencode/resources/session/__init__.py | 89 +
src/opencode/resources/session/form.py | 547 +
src/opencode/resources/session/inbox.py | 348 +
.../session/instructions/__init__.py | 33 +
.../resources/session/instructions/entry.py | 355 +
.../session/instructions/instructions.py | 105 +
src/opencode/resources/session/message.py | 176 +
src/opencode/resources/session/revert.py | 349 +
src/opencode/resources/session/session.py | 3011 ++
src/opencode/resources/shell.py | 710 +-
src/opencode/resources/skill.py | 198 +-
src/opencode/resources/vcs/__init__.py | 33 +
src/opencode/resources/vcs/branch.py | 189 +
src/opencode/resources/vcs/vcs.py | 591 +
src/opencode/resources/voice.py | 218 +-
src/opencode/resources/websearch.py | 350 +-
src/opencode/resources/worktree.py | 585 +-
src/opencode/types/__init__.py | 1854 +-
src/opencode/types/agent_get_params.py | 19 +
src/opencode/types/agent_get_response.py | 29 +
src/opencode/types/agent_list_params.py | 19 +
src/opencode/types/agent_list_response.py | 29 +
src/opencode/types/command_list_params.py | 19 +
src/opencode/types/command_list_response.py | 29 +
src/opencode/types/config_get_params.py | 19 +
src/opencode/types/config_get_response.py | 10 +
src/opencode/types/config_shells_response.py | 10 +
src/opencode/types/config_update_params.py | 12 +
.../types/credential_create_params.py | 24 +
.../types/credential_create_response.py | 26 +
.../types/credential_list_response.py | 26 +
.../types/credential_update_params.py | 11 +
src/opencode/types/debug/__init__.py | 6 +
.../types/debug/location_evict_params.py | 19 +
.../types/debug/location_list_response.py | 10 +
src/opencode/types/experimental/__init__.py | 17 +
.../persistent_pty_connect_token_response.py | 26 +
.../persistent_pty_create_params.py | 33 +
.../persistent_pty_create_response.py | 26 +
.../persistent_pty_get_response.py | 26 +
.../persistent_pty_handoff_response.py | 26 +
.../persistent_pty_list_response.py | 26 +
.../persistent_pty_read_params.py | 14 +
.../persistent_pty_read_response.py | 26 +
.../persistent_pty_snapshot_response.py | 26 +
.../persistent_pty_update_params.py | 25 +
.../persistent_pty_update_response.py | 26 +
src/opencode/types/file_find_params.py | 25 +
src/opencode/types/file_find_response.py | 29 +
src/opencode/types/file_list_params.py | 25 +
src/opencode/types/file_list_response.py | 29 +
src/opencode/types/file_read_params.py | 19 +
src/opencode/types/file_write_params.py | 25 +
src/opencode/types/file_write_response.py | 29 +
src/opencode/types/form_list_params.py | 19 +
src/opencode/types/form_list_response.py | 29 +
src/opencode/types/generate_text_params.py | 16 +
src/opencode/types/integration/__init__.py | 18 +
.../integration/command_cancel_params.py | 24 +
.../integration/command_connect_params.py | 26 +
.../integration/command_connect_response.py | 29 +
.../integration/command_status_params.py | 24 +
.../integration/command_status_response.py | 29 +
.../integration/connect_external_params.py | 29 +
.../types/integration/connect_key_params.py | 27 +
.../types/integration/oauth_cancel_params.py | 24 +
.../integration/oauth_complete_params.py | 26 +
.../types/integration/oauth_connect_params.py | 29 +
.../integration/oauth_connect_response.py | 29 +
.../types/integration/oauth_status_params.py | 24 +
.../integration/oauth_status_response.py | 29 +
.../types/integration/wellknown_add_params.py | 21 +
src/opencode/types/integration_get_params.py | 19 +
.../types/integration_get_response.py | 29 +
src/opencode/types/integration_list_params.py | 19 +
.../types/integration_list_response.py | 29 +
src/opencode/types/location_get_params.py | 19 +
src/opencode/types/mcp/__init__.py | 6 +
.../types/mcp/resource_catalog_params.py | 19 +
.../types/mcp/resource_catalog_response.py | 29 +
src/opencode/types/mcp_add_params.py | 27 +
src/opencode/types/mcp_connect_params.py | 19 +
src/opencode/types/mcp_disconnect_params.py | 19 +
src/opencode/types/mcp_list_params.py | 19 +
src/opencode/types/mcp_list_response.py | 29 +
src/opencode/types/mcp_remove_params.py | 19 +
src/opencode/types/message_list_params.py | 45 +
src/opencode/types/migration/__init__.py | 5 +
.../types/migration/v1_status_response.py | 91 +
src/opencode/types/model_default_params.py | 19 +
src/opencode/types/model_default_response.py | 29 +
src/opencode/types/model_list_params.py | 19 +
src/opencode/types/model_list_response.py | 29 +
src/opencode/types/permission/__init__.py | 10 +
.../types/permission/request_list_params.py | 19 +
.../types/permission/request_list_response.py | 29 +
.../types/permission/saved_list_params.py | 15 +
.../types/permission/saved_list_response.py | 26 +
.../types/permission/saved_update_params.py | 14 +
.../types/permission/saved_update_response.py | 26 +
.../types/permission_create_params.py | 29 +
.../types/permission_create_response.py | 45 +
src/opencode/types/permission_get_response.py | 26 +
.../types/permission_list_response.py | 26 +
src/opencode/types/permission_reply_params.py | 20 +
src/opencode/types/plugin_check_params.py | 21 +
src/opencode/types/plugin_check_response.py | 29 +
src/opencode/types/plugin_list_params.py | 19 +
src/opencode/types/plugin_list_response.py | 29 +
src/opencode/types/plugin_update_params.py | 23 +
src/opencode/types/project_list_params.py | 12 +
src/opencode/types/project_list_response.py | 10 +
src/opencode/types/project_update_params.py | 21 +
src/opencode/types/provider_get_params.py | 19 +
src/opencode/types/provider_get_response.py | 29 +
src/opencode/types/provider_list_params.py | 19 +
src/opencode/types/provider_list_response.py | 29 +
src/opencode/types/pty/__init__.py | 6 +
.../types/pty/connect_token_params.py | 23 +
.../types/pty/connect_token_response.py | 29 +
src/opencode/types/pty_create_params.py | 31 +
src/opencode/types/pty_create_response.py | 29 +
src/opencode/types/pty_get_params.py | 19 +
src/opencode/types/pty_get_response.py | 29 +
src/opencode/types/pty_list_params.py | 19 +
src/opencode/types/pty_list_response.py | 29 +
src/opencode/types/pty_remove_params.py | 19 +
src/opencode/types/pty_update_params.py | 32 +
src/opencode/types/pty_update_response.py | 29 +
src/opencode/types/reference_list_params.py | 19 +
src/opencode/types/reference_list_response.py | 29 +
src/opencode/types/rpc_call_params.py | 25 +
src/opencode/types/session/__init__.py | 15 +
.../types/session/form_cancel_params.py | 16 +
.../types/session/form_create_params.py | 22 +
.../types/session/form_create_response.py | 26 +
.../types/session/form_get_response.py | 26 +
.../types/session/form_list_response.py | 26 +
.../types/session/form_reply_params.py | 16 +
.../types/session/inbox_list_response.py | 26 +
.../types/session/inbox_update_params.py | 17 +
.../types/session/instructions/__init__.py | 6 +
.../instructions/entry_list_response.py | 26 +
.../session/instructions/entry_put_params.py | 16 +
.../types/session/message_get_response.py | 26 +
.../types/session/revert_stage_params.py | 17 +
.../types/session/revert_stage_response.py | 26 +
src/opencode/types/session_active_response.py | 25 +
src/opencode/types/session_command_params.py | 27 +
src/opencode/types/session_compact_params.py | 17 +
.../types/session_compact_response.py | 26 +
.../types/session_context_response.py | 26 +
src/opencode/types/session_create_params.py | 34 +
src/opencode/types/session_create_response.py | 26 +
src/opencode/types/session_diff_params.py | 28 +
src/opencode/types/session_diff_response.py | 26 +
.../types/session_environment_params.py | 12 +
src/opencode/types/session_export_params.py | 12 +
src/opencode/types/session_export_response.py | 26 +
src/opencode/types/session_fork_params.py | 14 +
src/opencode/types/session_fork_response.py | 26 +
src/opencode/types/session_generate_params.py | 11 +
src/opencode/types/session_get_response.py | 26 +
src/opencode/types/session_import__params.py | 19 +
.../types/session_import__response.py | 26 +
.../types/session_interrupt_params.py | 12 +
src/opencode/types/session_list_params.py | 40 +
src/opencode/types/session_log_params.py | 16 +
src/opencode/types/session_move_params.py | 17 +
src/opencode/types/session_prompt_params.py | 32 +
src/opencode/types/session_prompt_response.py | 26 +
src/opencode/types/session_shell_params.py | 16 +
src/opencode/types/session_skill_params.py | 16 +
src/opencode/types/session_stats_params.py | 23 +
src/opencode/types/session_stats_response.py | 26 +
.../types/session_switch_agent_params.py | 13 +
.../types/session_switch_model_params.py | 13 +
.../types/session_synthetic_params.py | 25 +
.../types/session_synthetic_response.py | 26 +
src/opencode/types/session_update_params.py | 25 +
src/opencode/types/session_view_params.py | 11 +
src/opencode/types/shared/__init__.py | 362 +
src/opencode/types/shared/absolute_path.py | 7 +
src/opencode/types/shared/agent_color.py | 7 +
src/opencode/types/shared/agent_id.py | 7 +
src/opencode/types/shared/agent_info.py | 51 +
.../types/shared/agent_updated_event.py | 38 +
src/opencode/types/shared/command_info.py | 27 +
.../types/shared/command_updated_event.py | 38 +
src/opencode/types/shared/config_agent.py | 83 +
src/opencode/types/shared/config_command.py | 56 +
src/opencode/types/shared/config_directory.py | 29 +
src/opencode/types/shared/config_document.py | 32 +
src/opencode/types/shared/config_entry.py | 11 +
.../types/shared/config_formatter_entry.py | 31 +
src/opencode/types/shared/config_info.py | 392 +
.../types/shared/config_lsp_server.py | 33 +
src/opencode/types/shared/config_model.py | 101 +
.../types/shared/config_model_capabilities.py | 34 +
.../types/shared/config_model_cost.py | 69 +
.../types/shared/config_model_reasoning.py | 74 +
.../types/shared/config_model_settings.py | 29 +
.../types/shared/config_plugin_entry.py | 27 +
src/opencode/types/shared/config_provider.py | 45 +
.../types/shared/config_provider_settings.py | 39 +
.../types/shared/config_reference_git.py | 31 +
.../types/shared/config_reference_local.py | 29 +
.../types/shared/config_shell_option.py | 29 +
.../types/shared/config_updated_event.py | 38 +
src/opencode/types/shared/config_warming.py | 29 +
.../types/shared/config_web_search_info.py | 29 +
src/opencode/types/shared/config_worktree.py | 25 +
.../shared/connection_credential_info.py | 36 +
.../types/shared/connection_env_info.py | 31 +
src/opencode/types/shared/connection_info.py | 11 +
.../types/shared/connection_status.py | 30 +
src/opencode/types/shared/credential_entry.py | 36 +
.../types/shared/credential_external.py | 31 +
src/opencode/types/shared/credential_id.py | 7 +
src/opencode/types/shared/credential_key.py | 30 +
.../types/shared/credential_o_auth.py | 37 +
.../types/shared/credential_switched_event.py | 58 +
.../types/shared/credential_updated_event.py | 38 +
src/opencode/types/shared/credential_value.py | 12 +
src/opencode/types/shared/event.py | 202 +
src/opencode/types/shared/event_id.py | 7 +
src/opencode/types/shared/event_seq.py | 7 +
src/opencode/types/shared/event_version.py | 7 +
src/opencode/types/shared/event_version10.py | 7 +
src/opencode/types/shared/file_diff_info.py | 34 +
.../types/shared/file_system_entry.py | 29 +
.../types/shared/file_system_write.py | 26 +
.../types/shared/filesystem_changed_event.py | 56 +
src/opencode/types/shared/form_answer.py | 10 +
.../types/shared/form_boolean_field.py | 41 +
.../types/shared/form_cancelled_event.py | 57 +
.../types/shared/form_created_event.py | 55 +
src/opencode/types/shared/form_detail.py | 39 +
.../types/shared/form_external_field.py | 34 +
src/opencode/types/shared/form_field.py | 17 +
src/opencode/types/shared/form_fields.py | 10 +
src/opencode/types/shared/form_fields1.py | 10 +
src/opencode/types/shared/form_fields2.py | 10 +
src/opencode/types/shared/form_fields3.py | 10 +
src/opencode/types/shared/form_id.py | 7 +
src/opencode/types/shared/form_info.py | 36 +
.../types/shared/form_integer_field.py | 45 +
src/opencode/types/shared/form_metadata.py | 8 +
.../types/shared/form_multiselect_field.py | 50 +
.../types/shared/form_number_field.py | 45 +
src/opencode/types/shared/form_option.py | 29 +
.../types/shared/form_replied_event.py | 60 +
src/opencode/types/shared/form_state.py | 66 +
.../types/shared/form_string_field.py | 56 +
src/opencode/types/shared/form_value.py | 8 +
src/opencode/types/shared/form_when.py | 30 +
.../types/shared/generate_text_response.py | 41 +
.../installation_update_available_event.py | 54 +
.../shared/installation_updated_event.py | 54 +
.../types/shared/instruction_entry_info.py | 30 +
.../types/shared/instruction_entry_key.py | 7 +
.../shared/instruction_entry_snapshot.py | 36 +
src/opencode/types/shared/instruction_hash.py | 7 +
.../types/shared/integration_attempt.py | 53 +
.../types/shared/integration_attempt_id.py | 7 +
.../shared/integration_attempt_status.py | 174 +
.../shared/integration_command_attempt.py | 47 +
.../integration_command_attempt_status.py | 176 +
.../shared/integration_command_method.py | 33 +
.../types/shared/integration_env_method.py | 28 +
.../shared/integration_external_method.py | 34 +
src/opencode/types/shared/integration_id.py | 7 +
src/opencode/types/shared/integration_info.py | 36 +
.../types/shared/integration_key_method.py | 31 +
.../types/shared/integration_method.py | 20 +
.../types/shared/integration_method_id.py | 7 +
.../types/shared/integration_o_auth_method.py | 34 +
.../types/shared/integration_updated_event.py | 38 +
.../types/shared/location_public_info.py | 49 +
.../types/shared/location_public_ref.py | 26 +
src/opencode/types/shared/location_ref.py | 29 +
.../types/shared/location_shutdown_event.py | 38 +
src/opencode/types/shared/mcp_local_config.py | 68 +
.../types/shared/mcp_o_auth_config.py | 35 +
src/opencode/types/shared/mcp_protocol.py | 7 +
.../types/shared/mcp_remote_config.py | 71 +
src/opencode/types/shared/mcp_resource.py | 33 +
.../types/shared/mcp_resource_catalog.py | 29 +
.../types/shared/mcp_resource_template.py | 33 +
.../shared/mcp_resources_changed_event.py | 54 +
src/opencode/types/shared/mcp_server.py | 38 +
.../types/shared/mcp_status_changed_event.py | 54 +
.../types/shared/mcp_status_connected.py | 26 +
.../types/shared/mcp_status_disabled.py | 26 +
.../types/shared/mcp_status_failed.py | 28 +
.../types/shared/mcp_status_needs_auth.py | 28 +
.../types/shared/mcp_status_pending.py | 26 +
.../types/shared/model_cache_format.py | 7 +
.../types/shared/model_capabilities.py | 31 +
.../types/shared/model_compatibility.py | 46 +
src/opencode/types/shared/model_cost.py | 69 +
src/opencode/types/shared/model_info.py | 102 +
.../types/shared/model_max_tokens_field.py | 7 +
.../types/shared/model_reasoning_field.py | 8 +
src/opencode/types/shared/model_ref.py | 30 +
src/opencode/types/shared/model_settings.py | 29 +
.../types/shared/model_updated_event.py | 38 +
src/opencode/types/shared/model_variant.py | 32 +
.../shared/models_dev_refreshed_event.py | 38 +
src/opencode/types/shared/money_usd.py | 7 +
.../shared/money_usd_per_million_tokens.py | 7 +
src/opencode/types/shared/pairing_code.py | 27 +
src/opencode/types/shared/pairing_session.py | 25 +
.../types/shared/permission_asked_event.py | 71 +
.../types/shared/permission_effect.py | 7 +
src/opencode/types/shared/permission_id.py | 7 +
.../types/shared/permission_replied_event.py | 61 +
src/opencode/types/shared/permission_reply.py | 7 +
.../types/shared/permission_request.py | 42 +
src/opencode/types/shared/permission_rule.py | 30 +
.../types/shared/permission_ruleset.py | 10 +
.../types/shared/permission_saved_id.py | 7 +
.../types/shared/permission_saved_info.py | 53 +
.../types/shared/permission_source.py | 30 +
.../shared/persistent_pty_added_event.py | 58 +
.../types/shared/persistent_pty_handoff.py | 32 +
.../types/shared/persistent_pty_info.py | 86 +
.../types/shared/persistent_pty_read_lines.py | 7 +
.../shared/persistent_pty_read_result.py | 74 +
.../shared/persistent_pty_removed_event.py | 58 +
.../types/shared/persistent_pty_snapshot.py | 50 +
src/opencode/types/shared/plugin_features.py | 30 +
src/opencode/types/shared/plugin_info.py | 34 +
src/opencode/types/shared/plugin_source.py | 93 +
src/opencode/types/shared/plugin_state.py | 49 +
.../types/shared/plugin_updated_event.py | 38 +
src/opencode/types/shared/project.py | 45 +
src/opencode/types/shared/project_commands.py | 26 +
src/opencode/types/shared/project_icon.py | 29 +
src/opencode/types/shared/project_id.py | 7 +
src/opencode/types/shared/project_time.py | 31 +
.../types/shared/project_updated_event.py | 74 +
src/opencode/types/shared/project_vcs.py | 7 +
.../types/shared/prompt_agent_attachment.py | 28 +
src/opencode/types/shared/prompt_base64.py | 7 +
.../types/shared/prompt_file_attachment.py | 38 +
.../types/shared/prompt_file_source.py | 47 +
.../shared/prompt_input_file_attachment.py | 32 +
.../shared/prompt_input_skill_attachment.py | 29 +
src/opencode/types/shared/prompt_mention.py | 29 +
.../types/shared/prompt_skill_attachment.py | 34 +
.../types/shared/provider_compaction.py | 45 +
.../types/shared/provider_compatibility.py | 42 +
.../types/shared/provider_encoding.py | 7 +
src/opencode/types/shared/provider_id.py | 7 +
src/opencode/types/shared/provider_info.py | 48 +
src/opencode/types/shared/provider_request.py | 30 +
.../types/shared/provider_settings.py | 39 +
.../types/shared/provider_transport.py | 7 +
.../types/shared/provider_updated_event.py | 38 +
src/opencode/types/shared/pty.py | 41 +
.../types/shared/pty_created_event.py | 55 +
.../types/shared/pty_deleted_event.py | 55 +
src/opencode/types/shared/pty_exited_event.py | 57 +
src/opencode/types/shared/pty_id.py | 7 +
.../types/shared/pty_ticket_connect_token.py | 27 +
.../types/shared/pty_updated_event.py | 55 +
.../types/shared/reference_git_source.py | 30 +
src/opencode/types/shared/reference_info.py | 35 +
.../types/shared/reference_local_source.py | 29 +
src/opencode/types/shared/reference_source.py | 11 +
.../types/shared/reference_updated_event.py | 38 +
src/opencode/types/shared/relative_path.py | 7 +
src/opencode/types/shared/rpc_event.py | 37 +
src/opencode/types/shared/rpc_output.py | 25 +
.../types/shared/server_connected_event.py | 36 +
src/opencode/types/shared/server_info.py | 65 +
.../shared/session_agent_selected_event.py | 84 +
.../types/shared/session_archived_event.py | 79 +
.../shared/session_compaction_delta_event.py | 57 +
.../shared/session_compaction_ended_event.py | 95 +
.../shared/session_compaction_failed_event.py | 93 +
.../session_compaction_started_event.py | 84 +
.../types/shared/session_created_event.py | 107 +
.../types/shared/session_deleted_event.py | 79 +
.../shared/session_execution_failed_event.py | 82 +
.../session_execution_interrupted_event.py | 81 +
.../shared/session_execution_started_event.py | 79 +
.../session_execution_succeeded_event.py | 79 +
.../types/shared/session_fork_boundary.py | 50 +
.../types/shared/session_forked_event.py | 89 +
.../types/shared/session_generate_response.py | 41 +
src/opencode/types/shared/session_id.py | 7 +
src/opencode/types/shared/session_id1.py | 7 +
.../types/shared/session_idle_event.py | 55 +
.../shared/session_inbox_cancelled_event.py | 82 +
.../types/shared/session_inbox_compaction.py | 56 +
.../session_inbox_compaction_payload.py | 9 +
.../shared/session_inbox_delivered_event.py | 82 +
.../types/shared/session_inbox_delivery.py | 7 +
.../session_inbox_delivery_changed_event.py | 85 +
.../shared/session_inbox_enqueued_event.py | 85 +
.../types/shared/session_inbox_info.py | 13 +
.../types/shared/session_inbox_item.py | 106 +
.../types/shared/session_inbox_move.py | 56 +
.../shared/session_inbox_move_payload.py | 32 +
.../shared/session_inbox_move_payload1.py | 32 +
.../types/shared/session_inbox_synthetic.py | 56 +
.../shared/session_inbox_synthetic_payload.py | 29 +
.../types/shared/session_inbox_user.py | 56 +
.../shared/session_inbox_user_payload.py | 36 +
src/opencode/types/shared/session_info.py | 110 +
.../session_instructions_updated_event.py | 86 +
.../shared/session_interrupt_response.py | 26 +
.../shared/session_message_agent_selected.py | 54 +
.../types/shared/session_message_assistant.py | 109 +
.../session_message_assistant_reasoning.py | 51 +
.../shared/session_message_assistant_retry.py | 30 +
.../shared/session_message_assistant_text.py | 31 +
.../shared/session_message_assistant_tool.py | 70 +
.../shared/session_message_compaction.py | 14 +
.../session_message_compaction_completed.py | 67 +
.../session_message_compaction_failed.py | 62 +
.../session_message_compaction_running.py | 55 +
.../types/shared/session_message_id.py | 7 +
.../types/shared/session_message_idle.py | 54 +
.../types/shared/session_message_info.py | 32 +
.../session_message_location_switched.py | 80 +
.../shared/session_message_model_selected.py | 54 +
.../types/shared/session_message_native.py | 8 +
.../types/shared/session_message_native1.py | 8 +
.../types/shared/session_message_native10.py | 8 +
.../types/shared/session_message_native11.py | 8 +
.../types/shared/session_message_native12.py | 8 +
.../types/shared/session_message_native2.py | 8 +
.../types/shared/session_message_native3.py | 8 +
.../types/shared/session_message_native4.py | 8 +
.../types/shared/session_message_native5.py | 8 +
.../types/shared/session_message_native6.py | 8 +
.../types/shared/session_message_native7.py | 8 +
.../types/shared/session_message_native8.py | 8 +
.../types/shared/session_message_native9.py | 8 +
.../types/shared/session_message_shell.py | 84 +
.../types/shared/session_message_skill.py | 57 +
.../types/shared/session_message_synthetic.py | 53 +
.../types/shared/session_message_system.py | 53 +
.../session_message_tool_state_completed.py | 33 +
.../session_message_tool_state_error.py | 36 +
.../session_message_tool_state_running.py | 30 +
.../session_message_tool_state_streaming.py | 28 +
.../types/shared/session_message_user.py | 60 +
.../types/shared/session_messages_response.py | 46 +
src/opencode/types/shared/session_metadata.py | 8 +
.../shared/session_metadata_updated_event.py | 82 +
.../shared/session_model_selected_event.py | 84 +
.../types/shared/session_moved_event.py | 87 +
.../types/shared/session_permissions_event.py | 82 +
.../shared/session_reasoning_delta_event.py | 62 +
.../shared/session_reasoning_ended_event.py | 89 +
.../shared/session_reasoning_started_event.py | 87 +
.../types/shared/session_renamed_event.py | 81 +
.../shared/session_retry_scheduled_event.py | 89 +
src/opencode/types/shared/session_revert.py | 34 +
.../shared/session_revert_cleared_event.py | 79 +
.../shared/session_revert_committed_event.py | 82 +
.../shared/session_revert_staged_event.py | 82 +
.../types/shared/session_shell_ended_event.py | 106 +
.../shared/session_shell_started_event.py | 82 +
.../shared/session_skill_activated_event.py | 87 +
.../types/shared/session_stats_activity.py | 27 +
.../types/shared/session_stats_info.py | 70 +
.../types/shared/session_stats_model_usage.py | 34 +
.../types/shared/session_stats_tool_totals.py | 31 +
.../types/shared/session_stats_tool_usage.py | 35 +
.../types/shared/session_stats_tools.py | 69 +
src/opencode/types/shared/session_status.py | 101 +
.../types/shared/session_status_updated.py | 58 +
.../types/shared/session_step_ended_event.py | 101 +
.../types/shared/session_step_failed_event.py | 104 +
.../shared/session_step_started_event.py | 93 +
.../shared/session_step_streamed_event.py | 82 +
.../types/shared/session_structured_error.py | 49 +
.../types/shared/session_synthetic_event.py | 85 +
.../types/shared/session_text_delta_event.py | 62 +
.../types/shared/session_text_ended_event.py | 89 +
.../shared/session_text_started_event.py | 84 +
.../types/shared/session_tool_called_event.py | 91 +
.../types/shared/session_tool_failed_event.py | 97 +
.../shared/session_tool_input_delta_event.py | 62 +
.../shared/session_tool_input_ended_event.py | 86 +
.../session_tool_input_started_event.py | 86 +
.../shared/session_tool_progress_event.py | 62 +
.../shared/session_tool_success_event.py | 94 +
.../types/shared/session_transfer_data.py | 29 +
.../types/shared/session_unarchived_event.py | 79 +
.../shared/session_usage_updated_event.py | 61 +
.../types/shared/session_viewed_event.py | 81 +
src/opencode/types/shared/sessions_cursor.py | 7 +
.../types/shared/sessions_response.py | 47 +
.../types/shared/shell_created_event.py | 55 +
.../types/shared/shell_deleted_event.py | 55 +
.../types/shared/shell_exited_event.py | 59 +
src/opencode/types/shared/shell_id.py | 7 +
src/opencode/types/shared/shell_info.py | 66 +
src/opencode/types/shared/skill_id.py | 7 +
src/opencode/types/shared/skill_info.py | 38 +
src/opencode/types/shared/skill_name.py | 7 +
.../types/shared/skill_updated_event.py | 38 +
src/opencode/types/shared/snapshot_id.py | 7 +
src/opencode/types/shared/token_usage_info.py | 49 +
src/opencode/types/shared/tool_content.py | 11 +
.../types/shared/tool_file_content.py | 32 +
.../types/shared/tool_text_content.py | 28 +
.../types/shared/tui_command_execute_event.py | 74 +
.../types/shared/tui_prompt_append_event.py | 54 +
.../types/shared/tui_session_select_event.py | 56 +
.../types/shared/tui_toast_show_event.py | 60 +
src/opencode/types/shared/vcs_base.py | 30 +
src/opencode/types/shared/vcs_branch.py | 27 +
src/opencode/types/shared/vcs_branch_list.py | 8 +
.../types/shared/vcs_branch_updated_event.py | 54 +
src/opencode/types/shared/vcs_file_status.py | 32 +
src/opencode/types/shared/vcs_info.py | 28 +
src/opencode/types/shared/vcs_mode.py | 7 +
.../types/shared/voice_session_response.py | 42 +
src/opencode/types/shared/voice_transport.py | 11 +
.../types/shared/voice_transport_input.py | 11 +
.../types/shared/voice_web_rtc_transport.py | 28 +
.../shared/voice_web_rtc_transport_input.py | 30 +
.../shared/voice_web_socket_transport.py | 32 +
.../voice_web_socket_transport_input.py | 26 +
src/opencode/types/shared/web_search_id.py | 7 +
.../types/shared/web_search_provider.py | 28 +
.../types/shared/web_search_response.py | 29 +
.../types/shared/web_search_result.py | 47 +
.../types/shared/websearch_updated_event.py | 38 +
src/opencode/types/shared/workspace_id.py | 7 +
.../types/shared/worktree_directory.py | 28 +
src/opencode/types/shared/worktree_info.py | 26 +
src/opencode/types/shared/worktree_list.py | 10 +
.../types/shared/worktree_resolved_event.py | 86 +
.../types/shared/worktree_updated_event.py | 55 +
src/opencode/types/shell_create_params.py | 29 +
src/opencode/types/shell_create_response.py | 29 +
src/opencode/types/shell_get_params.py | 19 +
src/opencode/types/shell_get_response.py | 29 +
src/opencode/types/shell_list_params.py | 19 +
src/opencode/types/shell_list_response.py | 29 +
src/opencode/types/shell_output_params.py | 23 +
src/opencode/types/shell_output_response.py | 50 +
src/opencode/types/shell_remove_params.py | 19 +
src/opencode/types/skill_list_params.py | 19 +
src/opencode/types/skill_list_response.py | 29 +
src/opencode/types/vc_base_params.py | 19 +
src/opencode/types/vc_base_response.py | 29 +
src/opencode/types/vc_diff_params.py | 27 +
src/opencode/types/vc_diff_response.py | 29 +
src/opencode/types/vc_get_params.py | 19 +
src/opencode/types/vc_get_response.py | 29 +
src/opencode/types/vc_init_params.py | 21 +
src/opencode/types/vc_status_params.py | 19 +
src/opencode/types/vc_status_response.py | 29 +
src/opencode/types/vcs/__init__.py | 6 +
src/opencode/types/vcs/branch_list_params.py | 23 +
.../types/vcs/branch_list_response.py | 29 +
src/opencode/types/voice_session_params.py | 18 +
.../types/websearch_providers_params.py | 19 +
.../types/websearch_providers_response.py | 29 +
src/opencode/types/websearch_query_params.py | 26 +
.../types/websearch_query_response.py | 29 +
src/opencode/types/worktree_create_params.py | 23 +
src/opencode/types/worktree_list_params.py | 14 +
src/opencode/types/worktree_refresh_params.py | 14 +
src/opencode/types/worktree_remove_params.py | 19 +
stainless.yml | 791 +
tests/__init__.py | 1 +
tests/api_resources/__init__.py | 1 +
tests/api_resources/debug/__init__.py | 1 +
tests/api_resources/debug/test_location.py | 142 +
tests/api_resources/experimental/__init__.py | 1 +
.../experimental/test_persistent_pty.py | 885 +
tests/api_resources/integration/__init__.py | 1 +
.../api_resources/integration/test_command.py | 368 +
.../api_resources/integration/test_connect.py | 242 +
tests/api_resources/integration/test_oauth.py | 490 +
.../integration/test_wellknown.py | 104 +
tests/api_resources/mcp/__init__.py | 1 +
tests/api_resources/mcp/test_resource.py | 92 +
tests/api_resources/migration/__init__.py | 1 +
tests/api_resources/migration/test_v1.py | 74 +
tests/api_resources/permission/__init__.py | 1 +
.../api_resources/permission/test_request.py | 92 +
tests/api_resources/permission/test_saved.py | 258 +
tests/api_resources/pty/__init__.py | 1 +
tests/api_resources/pty/test_connect.py | 122 +
tests/api_resources/session/__init__.py | 1 +
.../session/instructions/__init__.py | 1 +
.../session/instructions/test_entry.py | 302 +
tests/api_resources/session/test_form.py | 530 +
tests/api_resources/session/test_inbox.py | 302 +
tests/api_resources/session/test_message.py | 120 +
tests/api_resources/session/test_revert.py | 278 +
tests/api_resources/test_agent.py | 188 +
tests/api_resources/test_command.py | 92 +
tests/api_resources/test_config.py | 204 +
tests/api_resources/test_credential.py | 400 +
tests/api_resources/test_event.py | 74 +
tests/api_resources/test_file.py | 366 +
tests/api_resources/test_form.py | 92 +
tests/api_resources/test_generate.py | 102 +
tests/api_resources/test_integration.py | 188 +
tests/api_resources/test_location.py | 142 +
tests/api_resources/test_mcp.py | 486 +
tests/api_resources/test_message.py | 122 +
tests/api_resources/test_model.py | 160 +
tests/api_resources/test_permission.py | 446 +
tests/api_resources/test_plugin.py | 244 +
tests/api_resources/test_project.py | 187 +
tests/api_resources/test_provider.py | 188 +
tests/api_resources/test_pty.py | 473 +
tests/api_resources/test_reference.py | 92 +
tests/api_resources/test_rpc.py | 144 +
tests/api_resources/test_server.py | 200 +
tests/api_resources/test_session.py | 2514 ++
tests/api_resources/test_shell.py | 477 +
tests/api_resources/test_skill.py | 92 +
tests/api_resources/test_vcs.py | 389 +
tests/api_resources/test_voice.py | 110 +
tests/api_resources/test_websearch.py | 176 +
tests/api_resources/test_worktree.py | 306 +
tests/api_resources/vcs/__init__.py | 1 +
tests/api_resources/vcs/test_branch.py | 96 +
tests/conftest.py | 100 +-
tests/sample_file.txt | 1 +
tests/test_agent_recipes.py | 23 +
tests/test_client.py | 2226 +-
tests/test_extract_files.py | 91 +
tests/test_files.py | 148 +
tests/test_models.py | 1042 +-
tests/test_qs.py | 78 +
tests/test_required_args.py | 111 +
tests/test_response.py | 368 +-
tests/test_streaming.py | 278 +-
tests/test_transform.py | 460 +
tests/test_utils/test_datetime_parse.py | 110 +
tests/test_utils/test_json.py | 126 +
tests/test_utils/test_path.py | 89 +
tests/test_utils/test_proxy.py | 34 +
tests/test_utils/test_typing.py | 73 +
tests/utils.py | 167 +
778 files changed, 98210 insertions(+), 8141 deletions(-)
create mode 100644 .github/workflows/publish-pypi.yml
create mode 100644 .github/workflows/release-doctor.yml
create mode 100644 .github/workflows/sdk.yml
create mode 100644 AGENTS.md
create mode 100644 examples/agent/agent_list.py
create mode 100644 examples/agent/command_list.py
create mode 100644 examples/agent/config_get.py
create mode 100644 examples/agent/credential_list.py
create mode 100644 examples/agent/debug_location_list.py
create mode 100644 examples/agent/experimental_persistent_pty_handoff.py
create mode 100644 examples/agent/file_list.py
create mode 100644 examples/agent/form_list.py
create mode 100644 examples/agent/generate_text.py
create mode 100644 examples/agent/integration_command_cancel.py
create mode 100644 examples/agent/integration_connect_external.py
create mode 100644 examples/agent/integration_list.py
create mode 100644 examples/agent/integration_oauth_cancel.py
create mode 100644 examples/agent/integration_wellknown_add.py
create mode 100644 examples/agent/location_get.py
create mode 100644 examples/agent/mcp_list.py
create mode 100644 examples/agent/mcp_resource_catalog.py
create mode 100644 examples/agent/migration_v1_status.py
create mode 100644 examples/agent/model_list.py
create mode 100644 examples/agent/permission_create.py
create mode 100644 examples/agent/permission_request_list.py
create mode 100644 examples/agent/permission_saved_list.py
create mode 100644 examples/agent/plugin_list.py
create mode 100644 examples/agent/project_list.py
create mode 100644 examples/agent/provider_list.py
create mode 100644 examples/agent/pty_connect_token.py
create mode 100644 examples/agent/pty_create.py
create mode 100644 examples/agent/reference_list.py
create mode 100644 examples/agent/rpc_call.py
create mode 100644 examples/agent/server_info.py
create mode 100644 examples/agent/session_create.py
create mode 100644 examples/agent/session_form_create.py
create mode 100644 examples/agent/session_inbox_update.py
create mode 100644 examples/agent/session_instructions_entry_list.py
create mode 100644 examples/agent/session_message_get.py
create mode 100644 examples/agent/session_revert_clear.py
create mode 100644 examples/agent/shell_list.py
create mode 100644 examples/agent/skill_list.py
create mode 100644 examples/agent/vcs_base.py
create mode 100644 examples/agent/vcs_branch_list.py
create mode 100644 examples/agent/voice_session.py
create mode 100644 examples/agent/websearch_providers.py
create mode 100644 examples/agent/worktree_create.py
create mode 100644 ferrule.lock
create mode 100644 llms.txt
create mode 100644 openapi.json
create mode 100644 scripts/utils/ruffen-docs.py
create mode 100644 skills/opencode/SKILL.md
create mode 100644 src/opencode/_agent/AGENTS.md
create mode 100644 src/opencode/_agent/SKILL.md
create mode 100644 src/opencode/_agent/llms.txt
create mode 100644 src/opencode/_compat.py
create mode 100644 src/opencode/_files.py
create mode 100644 src/opencode/_utils/__init__.py
create mode 100644 src/opencode/_utils/_compat.py
create mode 100644 src/opencode/_utils/_datetime_parse.py
create mode 100644 src/opencode/_utils/_json.py
create mode 100644 src/opencode/_utils/_logs.py
create mode 100644 src/opencode/_utils/_path.py
create mode 100644 src/opencode/_utils/_proxy.py
create mode 100644 src/opencode/_utils/_reflection.py
create mode 100644 src/opencode/_utils/_resources_proxy.py
create mode 100644 src/opencode/_utils/_streams.py
create mode 100644 src/opencode/_utils/_sync.py
create mode 100644 src/opencode/_utils/_transform.py
create mode 100644 src/opencode/_utils/_typing.py
create mode 100644 src/opencode/_utils/_utils.py
create mode 100644 src/opencode/pagination.py
create mode 100644 src/opencode/resources/debug/__init__.py
create mode 100644 src/opencode/resources/debug/debug.py
create mode 100644 src/opencode/resources/debug/location.py
create mode 100644 src/opencode/resources/experimental/__init__.py
create mode 100644 src/opencode/resources/experimental/experimental.py
create mode 100644 src/opencode/resources/experimental/persistent_pty.py
create mode 100644 src/opencode/resources/integration/__init__.py
create mode 100644 src/opencode/resources/integration/command.py
create mode 100644 src/opencode/resources/integration/connect.py
create mode 100644 src/opencode/resources/integration/integration.py
create mode 100644 src/opencode/resources/integration/oauth.py
create mode 100644 src/opencode/resources/integration/wellknown.py
create mode 100644 src/opencode/resources/mcp/__init__.py
create mode 100644 src/opencode/resources/mcp/mcp.py
create mode 100644 src/opencode/resources/mcp/resource.py
create mode 100644 src/opencode/resources/migration/__init__.py
create mode 100644 src/opencode/resources/migration/migration.py
create mode 100644 src/opencode/resources/migration/v1.py
create mode 100644 src/opencode/resources/permission/__init__.py
create mode 100644 src/opencode/resources/permission/permission.py
create mode 100644 src/opencode/resources/permission/request.py
create mode 100644 src/opencode/resources/permission/saved.py
create mode 100644 src/opencode/resources/pty/__init__.py
create mode 100644 src/opencode/resources/pty/connect.py
create mode 100644 src/opencode/resources/pty/pty.py
create mode 100644 src/opencode/resources/session/__init__.py
create mode 100644 src/opencode/resources/session/form.py
create mode 100644 src/opencode/resources/session/inbox.py
create mode 100644 src/opencode/resources/session/instructions/__init__.py
create mode 100644 src/opencode/resources/session/instructions/entry.py
create mode 100644 src/opencode/resources/session/instructions/instructions.py
create mode 100644 src/opencode/resources/session/message.py
create mode 100644 src/opencode/resources/session/revert.py
create mode 100644 src/opencode/resources/session/session.py
create mode 100644 src/opencode/resources/vcs/__init__.py
create mode 100644 src/opencode/resources/vcs/branch.py
create mode 100644 src/opencode/resources/vcs/vcs.py
create mode 100644 src/opencode/types/agent_get_params.py
create mode 100644 src/opencode/types/agent_get_response.py
create mode 100644 src/opencode/types/agent_list_params.py
create mode 100644 src/opencode/types/agent_list_response.py
create mode 100644 src/opencode/types/command_list_params.py
create mode 100644 src/opencode/types/command_list_response.py
create mode 100644 src/opencode/types/config_get_params.py
create mode 100644 src/opencode/types/config_get_response.py
create mode 100644 src/opencode/types/config_shells_response.py
create mode 100644 src/opencode/types/config_update_params.py
create mode 100644 src/opencode/types/credential_create_params.py
create mode 100644 src/opencode/types/credential_create_response.py
create mode 100644 src/opencode/types/credential_list_response.py
create mode 100644 src/opencode/types/credential_update_params.py
create mode 100644 src/opencode/types/debug/__init__.py
create mode 100644 src/opencode/types/debug/location_evict_params.py
create mode 100644 src/opencode/types/debug/location_list_response.py
create mode 100644 src/opencode/types/experimental/__init__.py
create mode 100644 src/opencode/types/experimental/persistent_pty_connect_token_response.py
create mode 100644 src/opencode/types/experimental/persistent_pty_create_params.py
create mode 100644 src/opencode/types/experimental/persistent_pty_create_response.py
create mode 100644 src/opencode/types/experimental/persistent_pty_get_response.py
create mode 100644 src/opencode/types/experimental/persistent_pty_handoff_response.py
create mode 100644 src/opencode/types/experimental/persistent_pty_list_response.py
create mode 100644 src/opencode/types/experimental/persistent_pty_read_params.py
create mode 100644 src/opencode/types/experimental/persistent_pty_read_response.py
create mode 100644 src/opencode/types/experimental/persistent_pty_snapshot_response.py
create mode 100644 src/opencode/types/experimental/persistent_pty_update_params.py
create mode 100644 src/opencode/types/experimental/persistent_pty_update_response.py
create mode 100644 src/opencode/types/file_find_params.py
create mode 100644 src/opencode/types/file_find_response.py
create mode 100644 src/opencode/types/file_list_params.py
create mode 100644 src/opencode/types/file_list_response.py
create mode 100644 src/opencode/types/file_read_params.py
create mode 100644 src/opencode/types/file_write_params.py
create mode 100644 src/opencode/types/file_write_response.py
create mode 100644 src/opencode/types/form_list_params.py
create mode 100644 src/opencode/types/form_list_response.py
create mode 100644 src/opencode/types/generate_text_params.py
create mode 100644 src/opencode/types/integration/__init__.py
create mode 100644 src/opencode/types/integration/command_cancel_params.py
create mode 100644 src/opencode/types/integration/command_connect_params.py
create mode 100644 src/opencode/types/integration/command_connect_response.py
create mode 100644 src/opencode/types/integration/command_status_params.py
create mode 100644 src/opencode/types/integration/command_status_response.py
create mode 100644 src/opencode/types/integration/connect_external_params.py
create mode 100644 src/opencode/types/integration/connect_key_params.py
create mode 100644 src/opencode/types/integration/oauth_cancel_params.py
create mode 100644 src/opencode/types/integration/oauth_complete_params.py
create mode 100644 src/opencode/types/integration/oauth_connect_params.py
create mode 100644 src/opencode/types/integration/oauth_connect_response.py
create mode 100644 src/opencode/types/integration/oauth_status_params.py
create mode 100644 src/opencode/types/integration/oauth_status_response.py
create mode 100644 src/opencode/types/integration/wellknown_add_params.py
create mode 100644 src/opencode/types/integration_get_params.py
create mode 100644 src/opencode/types/integration_get_response.py
create mode 100644 src/opencode/types/integration_list_params.py
create mode 100644 src/opencode/types/integration_list_response.py
create mode 100644 src/opencode/types/location_get_params.py
create mode 100644 src/opencode/types/mcp/__init__.py
create mode 100644 src/opencode/types/mcp/resource_catalog_params.py
create mode 100644 src/opencode/types/mcp/resource_catalog_response.py
create mode 100644 src/opencode/types/mcp_add_params.py
create mode 100644 src/opencode/types/mcp_connect_params.py
create mode 100644 src/opencode/types/mcp_disconnect_params.py
create mode 100644 src/opencode/types/mcp_list_params.py
create mode 100644 src/opencode/types/mcp_list_response.py
create mode 100644 src/opencode/types/mcp_remove_params.py
create mode 100644 src/opencode/types/message_list_params.py
create mode 100644 src/opencode/types/migration/__init__.py
create mode 100644 src/opencode/types/migration/v1_status_response.py
create mode 100644 src/opencode/types/model_default_params.py
create mode 100644 src/opencode/types/model_default_response.py
create mode 100644 src/opencode/types/model_list_params.py
create mode 100644 src/opencode/types/model_list_response.py
create mode 100644 src/opencode/types/permission/__init__.py
create mode 100644 src/opencode/types/permission/request_list_params.py
create mode 100644 src/opencode/types/permission/request_list_response.py
create mode 100644 src/opencode/types/permission/saved_list_params.py
create mode 100644 src/opencode/types/permission/saved_list_response.py
create mode 100644 src/opencode/types/permission/saved_update_params.py
create mode 100644 src/opencode/types/permission/saved_update_response.py
create mode 100644 src/opencode/types/permission_create_params.py
create mode 100644 src/opencode/types/permission_create_response.py
create mode 100644 src/opencode/types/permission_get_response.py
create mode 100644 src/opencode/types/permission_list_response.py
create mode 100644 src/opencode/types/permission_reply_params.py
create mode 100644 src/opencode/types/plugin_check_params.py
create mode 100644 src/opencode/types/plugin_check_response.py
create mode 100644 src/opencode/types/plugin_list_params.py
create mode 100644 src/opencode/types/plugin_list_response.py
create mode 100644 src/opencode/types/plugin_update_params.py
create mode 100644 src/opencode/types/project_list_params.py
create mode 100644 src/opencode/types/project_list_response.py
create mode 100644 src/opencode/types/project_update_params.py
create mode 100644 src/opencode/types/provider_get_params.py
create mode 100644 src/opencode/types/provider_get_response.py
create mode 100644 src/opencode/types/provider_list_params.py
create mode 100644 src/opencode/types/provider_list_response.py
create mode 100644 src/opencode/types/pty/__init__.py
create mode 100644 src/opencode/types/pty/connect_token_params.py
create mode 100644 src/opencode/types/pty/connect_token_response.py
create mode 100644 src/opencode/types/pty_create_params.py
create mode 100644 src/opencode/types/pty_create_response.py
create mode 100644 src/opencode/types/pty_get_params.py
create mode 100644 src/opencode/types/pty_get_response.py
create mode 100644 src/opencode/types/pty_list_params.py
create mode 100644 src/opencode/types/pty_list_response.py
create mode 100644 src/opencode/types/pty_remove_params.py
create mode 100644 src/opencode/types/pty_update_params.py
create mode 100644 src/opencode/types/pty_update_response.py
create mode 100644 src/opencode/types/reference_list_params.py
create mode 100644 src/opencode/types/reference_list_response.py
create mode 100644 src/opencode/types/rpc_call_params.py
create mode 100644 src/opencode/types/session/__init__.py
create mode 100644 src/opencode/types/session/form_cancel_params.py
create mode 100644 src/opencode/types/session/form_create_params.py
create mode 100644 src/opencode/types/session/form_create_response.py
create mode 100644 src/opencode/types/session/form_get_response.py
create mode 100644 src/opencode/types/session/form_list_response.py
create mode 100644 src/opencode/types/session/form_reply_params.py
create mode 100644 src/opencode/types/session/inbox_list_response.py
create mode 100644 src/opencode/types/session/inbox_update_params.py
create mode 100644 src/opencode/types/session/instructions/__init__.py
create mode 100644 src/opencode/types/session/instructions/entry_list_response.py
create mode 100644 src/opencode/types/session/instructions/entry_put_params.py
create mode 100644 src/opencode/types/session/message_get_response.py
create mode 100644 src/opencode/types/session/revert_stage_params.py
create mode 100644 src/opencode/types/session/revert_stage_response.py
create mode 100644 src/opencode/types/session_active_response.py
create mode 100644 src/opencode/types/session_command_params.py
create mode 100644 src/opencode/types/session_compact_params.py
create mode 100644 src/opencode/types/session_compact_response.py
create mode 100644 src/opencode/types/session_context_response.py
create mode 100644 src/opencode/types/session_create_params.py
create mode 100644 src/opencode/types/session_create_response.py
create mode 100644 src/opencode/types/session_diff_params.py
create mode 100644 src/opencode/types/session_diff_response.py
create mode 100644 src/opencode/types/session_environment_params.py
create mode 100644 src/opencode/types/session_export_params.py
create mode 100644 src/opencode/types/session_export_response.py
create mode 100644 src/opencode/types/session_fork_params.py
create mode 100644 src/opencode/types/session_fork_response.py
create mode 100644 src/opencode/types/session_generate_params.py
create mode 100644 src/opencode/types/session_get_response.py
create mode 100644 src/opencode/types/session_import__params.py
create mode 100644 src/opencode/types/session_import__response.py
create mode 100644 src/opencode/types/session_interrupt_params.py
create mode 100644 src/opencode/types/session_list_params.py
create mode 100644 src/opencode/types/session_log_params.py
create mode 100644 src/opencode/types/session_move_params.py
create mode 100644 src/opencode/types/session_prompt_params.py
create mode 100644 src/opencode/types/session_prompt_response.py
create mode 100644 src/opencode/types/session_shell_params.py
create mode 100644 src/opencode/types/session_skill_params.py
create mode 100644 src/opencode/types/session_stats_params.py
create mode 100644 src/opencode/types/session_stats_response.py
create mode 100644 src/opencode/types/session_switch_agent_params.py
create mode 100644 src/opencode/types/session_switch_model_params.py
create mode 100644 src/opencode/types/session_synthetic_params.py
create mode 100644 src/opencode/types/session_synthetic_response.py
create mode 100644 src/opencode/types/session_update_params.py
create mode 100644 src/opencode/types/session_view_params.py
create mode 100644 src/opencode/types/shared/__init__.py
create mode 100644 src/opencode/types/shared/absolute_path.py
create mode 100644 src/opencode/types/shared/agent_color.py
create mode 100644 src/opencode/types/shared/agent_id.py
create mode 100644 src/opencode/types/shared/agent_info.py
create mode 100644 src/opencode/types/shared/agent_updated_event.py
create mode 100644 src/opencode/types/shared/command_info.py
create mode 100644 src/opencode/types/shared/command_updated_event.py
create mode 100644 src/opencode/types/shared/config_agent.py
create mode 100644 src/opencode/types/shared/config_command.py
create mode 100644 src/opencode/types/shared/config_directory.py
create mode 100644 src/opencode/types/shared/config_document.py
create mode 100644 src/opencode/types/shared/config_entry.py
create mode 100644 src/opencode/types/shared/config_formatter_entry.py
create mode 100644 src/opencode/types/shared/config_info.py
create mode 100644 src/opencode/types/shared/config_lsp_server.py
create mode 100644 src/opencode/types/shared/config_model.py
create mode 100644 src/opencode/types/shared/config_model_capabilities.py
create mode 100644 src/opencode/types/shared/config_model_cost.py
create mode 100644 src/opencode/types/shared/config_model_reasoning.py
create mode 100644 src/opencode/types/shared/config_model_settings.py
create mode 100644 src/opencode/types/shared/config_plugin_entry.py
create mode 100644 src/opencode/types/shared/config_provider.py
create mode 100644 src/opencode/types/shared/config_provider_settings.py
create mode 100644 src/opencode/types/shared/config_reference_git.py
create mode 100644 src/opencode/types/shared/config_reference_local.py
create mode 100644 src/opencode/types/shared/config_shell_option.py
create mode 100644 src/opencode/types/shared/config_updated_event.py
create mode 100644 src/opencode/types/shared/config_warming.py
create mode 100644 src/opencode/types/shared/config_web_search_info.py
create mode 100644 src/opencode/types/shared/config_worktree.py
create mode 100644 src/opencode/types/shared/connection_credential_info.py
create mode 100644 src/opencode/types/shared/connection_env_info.py
create mode 100644 src/opencode/types/shared/connection_info.py
create mode 100644 src/opencode/types/shared/connection_status.py
create mode 100644 src/opencode/types/shared/credential_entry.py
create mode 100644 src/opencode/types/shared/credential_external.py
create mode 100644 src/opencode/types/shared/credential_id.py
create mode 100644 src/opencode/types/shared/credential_key.py
create mode 100644 src/opencode/types/shared/credential_o_auth.py
create mode 100644 src/opencode/types/shared/credential_switched_event.py
create mode 100644 src/opencode/types/shared/credential_updated_event.py
create mode 100644 src/opencode/types/shared/credential_value.py
create mode 100644 src/opencode/types/shared/event.py
create mode 100644 src/opencode/types/shared/event_id.py
create mode 100644 src/opencode/types/shared/event_seq.py
create mode 100644 src/opencode/types/shared/event_version.py
create mode 100644 src/opencode/types/shared/event_version10.py
create mode 100644 src/opencode/types/shared/file_diff_info.py
create mode 100644 src/opencode/types/shared/file_system_entry.py
create mode 100644 src/opencode/types/shared/file_system_write.py
create mode 100644 src/opencode/types/shared/filesystem_changed_event.py
create mode 100644 src/opencode/types/shared/form_answer.py
create mode 100644 src/opencode/types/shared/form_boolean_field.py
create mode 100644 src/opencode/types/shared/form_cancelled_event.py
create mode 100644 src/opencode/types/shared/form_created_event.py
create mode 100644 src/opencode/types/shared/form_detail.py
create mode 100644 src/opencode/types/shared/form_external_field.py
create mode 100644 src/opencode/types/shared/form_field.py
create mode 100644 src/opencode/types/shared/form_fields.py
create mode 100644 src/opencode/types/shared/form_fields1.py
create mode 100644 src/opencode/types/shared/form_fields2.py
create mode 100644 src/opencode/types/shared/form_fields3.py
create mode 100644 src/opencode/types/shared/form_id.py
create mode 100644 src/opencode/types/shared/form_info.py
create mode 100644 src/opencode/types/shared/form_integer_field.py
create mode 100644 src/opencode/types/shared/form_metadata.py
create mode 100644 src/opencode/types/shared/form_multiselect_field.py
create mode 100644 src/opencode/types/shared/form_number_field.py
create mode 100644 src/opencode/types/shared/form_option.py
create mode 100644 src/opencode/types/shared/form_replied_event.py
create mode 100644 src/opencode/types/shared/form_state.py
create mode 100644 src/opencode/types/shared/form_string_field.py
create mode 100644 src/opencode/types/shared/form_value.py
create mode 100644 src/opencode/types/shared/form_when.py
create mode 100644 src/opencode/types/shared/generate_text_response.py
create mode 100644 src/opencode/types/shared/installation_update_available_event.py
create mode 100644 src/opencode/types/shared/installation_updated_event.py
create mode 100644 src/opencode/types/shared/instruction_entry_info.py
create mode 100644 src/opencode/types/shared/instruction_entry_key.py
create mode 100644 src/opencode/types/shared/instruction_entry_snapshot.py
create mode 100644 src/opencode/types/shared/instruction_hash.py
create mode 100644 src/opencode/types/shared/integration_attempt.py
create mode 100644 src/opencode/types/shared/integration_attempt_id.py
create mode 100644 src/opencode/types/shared/integration_attempt_status.py
create mode 100644 src/opencode/types/shared/integration_command_attempt.py
create mode 100644 src/opencode/types/shared/integration_command_attempt_status.py
create mode 100644 src/opencode/types/shared/integration_command_method.py
create mode 100644 src/opencode/types/shared/integration_env_method.py
create mode 100644 src/opencode/types/shared/integration_external_method.py
create mode 100644 src/opencode/types/shared/integration_id.py
create mode 100644 src/opencode/types/shared/integration_info.py
create mode 100644 src/opencode/types/shared/integration_key_method.py
create mode 100644 src/opencode/types/shared/integration_method.py
create mode 100644 src/opencode/types/shared/integration_method_id.py
create mode 100644 src/opencode/types/shared/integration_o_auth_method.py
create mode 100644 src/opencode/types/shared/integration_updated_event.py
create mode 100644 src/opencode/types/shared/location_public_info.py
create mode 100644 src/opencode/types/shared/location_public_ref.py
create mode 100644 src/opencode/types/shared/location_ref.py
create mode 100644 src/opencode/types/shared/location_shutdown_event.py
create mode 100644 src/opencode/types/shared/mcp_local_config.py
create mode 100644 src/opencode/types/shared/mcp_o_auth_config.py
create mode 100644 src/opencode/types/shared/mcp_protocol.py
create mode 100644 src/opencode/types/shared/mcp_remote_config.py
create mode 100644 src/opencode/types/shared/mcp_resource.py
create mode 100644 src/opencode/types/shared/mcp_resource_catalog.py
create mode 100644 src/opencode/types/shared/mcp_resource_template.py
create mode 100644 src/opencode/types/shared/mcp_resources_changed_event.py
create mode 100644 src/opencode/types/shared/mcp_server.py
create mode 100644 src/opencode/types/shared/mcp_status_changed_event.py
create mode 100644 src/opencode/types/shared/mcp_status_connected.py
create mode 100644 src/opencode/types/shared/mcp_status_disabled.py
create mode 100644 src/opencode/types/shared/mcp_status_failed.py
create mode 100644 src/opencode/types/shared/mcp_status_needs_auth.py
create mode 100644 src/opencode/types/shared/mcp_status_pending.py
create mode 100644 src/opencode/types/shared/model_cache_format.py
create mode 100644 src/opencode/types/shared/model_capabilities.py
create mode 100644 src/opencode/types/shared/model_compatibility.py
create mode 100644 src/opencode/types/shared/model_cost.py
create mode 100644 src/opencode/types/shared/model_info.py
create mode 100644 src/opencode/types/shared/model_max_tokens_field.py
create mode 100644 src/opencode/types/shared/model_reasoning_field.py
create mode 100644 src/opencode/types/shared/model_ref.py
create mode 100644 src/opencode/types/shared/model_settings.py
create mode 100644 src/opencode/types/shared/model_updated_event.py
create mode 100644 src/opencode/types/shared/model_variant.py
create mode 100644 src/opencode/types/shared/models_dev_refreshed_event.py
create mode 100644 src/opencode/types/shared/money_usd.py
create mode 100644 src/opencode/types/shared/money_usd_per_million_tokens.py
create mode 100644 src/opencode/types/shared/pairing_code.py
create mode 100644 src/opencode/types/shared/pairing_session.py
create mode 100644 src/opencode/types/shared/permission_asked_event.py
create mode 100644 src/opencode/types/shared/permission_effect.py
create mode 100644 src/opencode/types/shared/permission_id.py
create mode 100644 src/opencode/types/shared/permission_replied_event.py
create mode 100644 src/opencode/types/shared/permission_reply.py
create mode 100644 src/opencode/types/shared/permission_request.py
create mode 100644 src/opencode/types/shared/permission_rule.py
create mode 100644 src/opencode/types/shared/permission_ruleset.py
create mode 100644 src/opencode/types/shared/permission_saved_id.py
create mode 100644 src/opencode/types/shared/permission_saved_info.py
create mode 100644 src/opencode/types/shared/permission_source.py
create mode 100644 src/opencode/types/shared/persistent_pty_added_event.py
create mode 100644 src/opencode/types/shared/persistent_pty_handoff.py
create mode 100644 src/opencode/types/shared/persistent_pty_info.py
create mode 100644 src/opencode/types/shared/persistent_pty_read_lines.py
create mode 100644 src/opencode/types/shared/persistent_pty_read_result.py
create mode 100644 src/opencode/types/shared/persistent_pty_removed_event.py
create mode 100644 src/opencode/types/shared/persistent_pty_snapshot.py
create mode 100644 src/opencode/types/shared/plugin_features.py
create mode 100644 src/opencode/types/shared/plugin_info.py
create mode 100644 src/opencode/types/shared/plugin_source.py
create mode 100644 src/opencode/types/shared/plugin_state.py
create mode 100644 src/opencode/types/shared/plugin_updated_event.py
create mode 100644 src/opencode/types/shared/project.py
create mode 100644 src/opencode/types/shared/project_commands.py
create mode 100644 src/opencode/types/shared/project_icon.py
create mode 100644 src/opencode/types/shared/project_id.py
create mode 100644 src/opencode/types/shared/project_time.py
create mode 100644 src/opencode/types/shared/project_updated_event.py
create mode 100644 src/opencode/types/shared/project_vcs.py
create mode 100644 src/opencode/types/shared/prompt_agent_attachment.py
create mode 100644 src/opencode/types/shared/prompt_base64.py
create mode 100644 src/opencode/types/shared/prompt_file_attachment.py
create mode 100644 src/opencode/types/shared/prompt_file_source.py
create mode 100644 src/opencode/types/shared/prompt_input_file_attachment.py
create mode 100644 src/opencode/types/shared/prompt_input_skill_attachment.py
create mode 100644 src/opencode/types/shared/prompt_mention.py
create mode 100644 src/opencode/types/shared/prompt_skill_attachment.py
create mode 100644 src/opencode/types/shared/provider_compaction.py
create mode 100644 src/opencode/types/shared/provider_compatibility.py
create mode 100644 src/opencode/types/shared/provider_encoding.py
create mode 100644 src/opencode/types/shared/provider_id.py
create mode 100644 src/opencode/types/shared/provider_info.py
create mode 100644 src/opencode/types/shared/provider_request.py
create mode 100644 src/opencode/types/shared/provider_settings.py
create mode 100644 src/opencode/types/shared/provider_transport.py
create mode 100644 src/opencode/types/shared/provider_updated_event.py
create mode 100644 src/opencode/types/shared/pty.py
create mode 100644 src/opencode/types/shared/pty_created_event.py
create mode 100644 src/opencode/types/shared/pty_deleted_event.py
create mode 100644 src/opencode/types/shared/pty_exited_event.py
create mode 100644 src/opencode/types/shared/pty_id.py
create mode 100644 src/opencode/types/shared/pty_ticket_connect_token.py
create mode 100644 src/opencode/types/shared/pty_updated_event.py
create mode 100644 src/opencode/types/shared/reference_git_source.py
create mode 100644 src/opencode/types/shared/reference_info.py
create mode 100644 src/opencode/types/shared/reference_local_source.py
create mode 100644 src/opencode/types/shared/reference_source.py
create mode 100644 src/opencode/types/shared/reference_updated_event.py
create mode 100644 src/opencode/types/shared/relative_path.py
create mode 100644 src/opencode/types/shared/rpc_event.py
create mode 100644 src/opencode/types/shared/rpc_output.py
create mode 100644 src/opencode/types/shared/server_connected_event.py
create mode 100644 src/opencode/types/shared/server_info.py
create mode 100644 src/opencode/types/shared/session_agent_selected_event.py
create mode 100644 src/opencode/types/shared/session_archived_event.py
create mode 100644 src/opencode/types/shared/session_compaction_delta_event.py
create mode 100644 src/opencode/types/shared/session_compaction_ended_event.py
create mode 100644 src/opencode/types/shared/session_compaction_failed_event.py
create mode 100644 src/opencode/types/shared/session_compaction_started_event.py
create mode 100644 src/opencode/types/shared/session_created_event.py
create mode 100644 src/opencode/types/shared/session_deleted_event.py
create mode 100644 src/opencode/types/shared/session_execution_failed_event.py
create mode 100644 src/opencode/types/shared/session_execution_interrupted_event.py
create mode 100644 src/opencode/types/shared/session_execution_started_event.py
create mode 100644 src/opencode/types/shared/session_execution_succeeded_event.py
create mode 100644 src/opencode/types/shared/session_fork_boundary.py
create mode 100644 src/opencode/types/shared/session_forked_event.py
create mode 100644 src/opencode/types/shared/session_generate_response.py
create mode 100644 src/opencode/types/shared/session_id.py
create mode 100644 src/opencode/types/shared/session_id1.py
create mode 100644 src/opencode/types/shared/session_idle_event.py
create mode 100644 src/opencode/types/shared/session_inbox_cancelled_event.py
create mode 100644 src/opencode/types/shared/session_inbox_compaction.py
create mode 100644 src/opencode/types/shared/session_inbox_compaction_payload.py
create mode 100644 src/opencode/types/shared/session_inbox_delivered_event.py
create mode 100644 src/opencode/types/shared/session_inbox_delivery.py
create mode 100644 src/opencode/types/shared/session_inbox_delivery_changed_event.py
create mode 100644 src/opencode/types/shared/session_inbox_enqueued_event.py
create mode 100644 src/opencode/types/shared/session_inbox_info.py
create mode 100644 src/opencode/types/shared/session_inbox_item.py
create mode 100644 src/opencode/types/shared/session_inbox_move.py
create mode 100644 src/opencode/types/shared/session_inbox_move_payload.py
create mode 100644 src/opencode/types/shared/session_inbox_move_payload1.py
create mode 100644 src/opencode/types/shared/session_inbox_synthetic.py
create mode 100644 src/opencode/types/shared/session_inbox_synthetic_payload.py
create mode 100644 src/opencode/types/shared/session_inbox_user.py
create mode 100644 src/opencode/types/shared/session_inbox_user_payload.py
create mode 100644 src/opencode/types/shared/session_info.py
create mode 100644 src/opencode/types/shared/session_instructions_updated_event.py
create mode 100644 src/opencode/types/shared/session_interrupt_response.py
create mode 100644 src/opencode/types/shared/session_message_agent_selected.py
create mode 100644 src/opencode/types/shared/session_message_assistant.py
create mode 100644 src/opencode/types/shared/session_message_assistant_reasoning.py
create mode 100644 src/opencode/types/shared/session_message_assistant_retry.py
create mode 100644 src/opencode/types/shared/session_message_assistant_text.py
create mode 100644 src/opencode/types/shared/session_message_assistant_tool.py
create mode 100644 src/opencode/types/shared/session_message_compaction.py
create mode 100644 src/opencode/types/shared/session_message_compaction_completed.py
create mode 100644 src/opencode/types/shared/session_message_compaction_failed.py
create mode 100644 src/opencode/types/shared/session_message_compaction_running.py
create mode 100644 src/opencode/types/shared/session_message_id.py
create mode 100644 src/opencode/types/shared/session_message_idle.py
create mode 100644 src/opencode/types/shared/session_message_info.py
create mode 100644 src/opencode/types/shared/session_message_location_switched.py
create mode 100644 src/opencode/types/shared/session_message_model_selected.py
create mode 100644 src/opencode/types/shared/session_message_native.py
create mode 100644 src/opencode/types/shared/session_message_native1.py
create mode 100644 src/opencode/types/shared/session_message_native10.py
create mode 100644 src/opencode/types/shared/session_message_native11.py
create mode 100644 src/opencode/types/shared/session_message_native12.py
create mode 100644 src/opencode/types/shared/session_message_native2.py
create mode 100644 src/opencode/types/shared/session_message_native3.py
create mode 100644 src/opencode/types/shared/session_message_native4.py
create mode 100644 src/opencode/types/shared/session_message_native5.py
create mode 100644 src/opencode/types/shared/session_message_native6.py
create mode 100644 src/opencode/types/shared/session_message_native7.py
create mode 100644 src/opencode/types/shared/session_message_native8.py
create mode 100644 src/opencode/types/shared/session_message_native9.py
create mode 100644 src/opencode/types/shared/session_message_shell.py
create mode 100644 src/opencode/types/shared/session_message_skill.py
create mode 100644 src/opencode/types/shared/session_message_synthetic.py
create mode 100644 src/opencode/types/shared/session_message_system.py
create mode 100644 src/opencode/types/shared/session_message_tool_state_completed.py
create mode 100644 src/opencode/types/shared/session_message_tool_state_error.py
create mode 100644 src/opencode/types/shared/session_message_tool_state_running.py
create mode 100644 src/opencode/types/shared/session_message_tool_state_streaming.py
create mode 100644 src/opencode/types/shared/session_message_user.py
create mode 100644 src/opencode/types/shared/session_messages_response.py
create mode 100644 src/opencode/types/shared/session_metadata.py
create mode 100644 src/opencode/types/shared/session_metadata_updated_event.py
create mode 100644 src/opencode/types/shared/session_model_selected_event.py
create mode 100644 src/opencode/types/shared/session_moved_event.py
create mode 100644 src/opencode/types/shared/session_permissions_event.py
create mode 100644 src/opencode/types/shared/session_reasoning_delta_event.py
create mode 100644 src/opencode/types/shared/session_reasoning_ended_event.py
create mode 100644 src/opencode/types/shared/session_reasoning_started_event.py
create mode 100644 src/opencode/types/shared/session_renamed_event.py
create mode 100644 src/opencode/types/shared/session_retry_scheduled_event.py
create mode 100644 src/opencode/types/shared/session_revert.py
create mode 100644 src/opencode/types/shared/session_revert_cleared_event.py
create mode 100644 src/opencode/types/shared/session_revert_committed_event.py
create mode 100644 src/opencode/types/shared/session_revert_staged_event.py
create mode 100644 src/opencode/types/shared/session_shell_ended_event.py
create mode 100644 src/opencode/types/shared/session_shell_started_event.py
create mode 100644 src/opencode/types/shared/session_skill_activated_event.py
create mode 100644 src/opencode/types/shared/session_stats_activity.py
create mode 100644 src/opencode/types/shared/session_stats_info.py
create mode 100644 src/opencode/types/shared/session_stats_model_usage.py
create mode 100644 src/opencode/types/shared/session_stats_tool_totals.py
create mode 100644 src/opencode/types/shared/session_stats_tool_usage.py
create mode 100644 src/opencode/types/shared/session_stats_tools.py
create mode 100644 src/opencode/types/shared/session_status.py
create mode 100644 src/opencode/types/shared/session_status_updated.py
create mode 100644 src/opencode/types/shared/session_step_ended_event.py
create mode 100644 src/opencode/types/shared/session_step_failed_event.py
create mode 100644 src/opencode/types/shared/session_step_started_event.py
create mode 100644 src/opencode/types/shared/session_step_streamed_event.py
create mode 100644 src/opencode/types/shared/session_structured_error.py
create mode 100644 src/opencode/types/shared/session_synthetic_event.py
create mode 100644 src/opencode/types/shared/session_text_delta_event.py
create mode 100644 src/opencode/types/shared/session_text_ended_event.py
create mode 100644 src/opencode/types/shared/session_text_started_event.py
create mode 100644 src/opencode/types/shared/session_tool_called_event.py
create mode 100644 src/opencode/types/shared/session_tool_failed_event.py
create mode 100644 src/opencode/types/shared/session_tool_input_delta_event.py
create mode 100644 src/opencode/types/shared/session_tool_input_ended_event.py
create mode 100644 src/opencode/types/shared/session_tool_input_started_event.py
create mode 100644 src/opencode/types/shared/session_tool_progress_event.py
create mode 100644 src/opencode/types/shared/session_tool_success_event.py
create mode 100644 src/opencode/types/shared/session_transfer_data.py
create mode 100644 src/opencode/types/shared/session_unarchived_event.py
create mode 100644 src/opencode/types/shared/session_usage_updated_event.py
create mode 100644 src/opencode/types/shared/session_viewed_event.py
create mode 100644 src/opencode/types/shared/sessions_cursor.py
create mode 100644 src/opencode/types/shared/sessions_response.py
create mode 100644 src/opencode/types/shared/shell_created_event.py
create mode 100644 src/opencode/types/shared/shell_deleted_event.py
create mode 100644 src/opencode/types/shared/shell_exited_event.py
create mode 100644 src/opencode/types/shared/shell_id.py
create mode 100644 src/opencode/types/shared/shell_info.py
create mode 100644 src/opencode/types/shared/skill_id.py
create mode 100644 src/opencode/types/shared/skill_info.py
create mode 100644 src/opencode/types/shared/skill_name.py
create mode 100644 src/opencode/types/shared/skill_updated_event.py
create mode 100644 src/opencode/types/shared/snapshot_id.py
create mode 100644 src/opencode/types/shared/token_usage_info.py
create mode 100644 src/opencode/types/shared/tool_content.py
create mode 100644 src/opencode/types/shared/tool_file_content.py
create mode 100644 src/opencode/types/shared/tool_text_content.py
create mode 100644 src/opencode/types/shared/tui_command_execute_event.py
create mode 100644 src/opencode/types/shared/tui_prompt_append_event.py
create mode 100644 src/opencode/types/shared/tui_session_select_event.py
create mode 100644 src/opencode/types/shared/tui_toast_show_event.py
create mode 100644 src/opencode/types/shared/vcs_base.py
create mode 100644 src/opencode/types/shared/vcs_branch.py
create mode 100644 src/opencode/types/shared/vcs_branch_list.py
create mode 100644 src/opencode/types/shared/vcs_branch_updated_event.py
create mode 100644 src/opencode/types/shared/vcs_file_status.py
create mode 100644 src/opencode/types/shared/vcs_info.py
create mode 100644 src/opencode/types/shared/vcs_mode.py
create mode 100644 src/opencode/types/shared/voice_session_response.py
create mode 100644 src/opencode/types/shared/voice_transport.py
create mode 100644 src/opencode/types/shared/voice_transport_input.py
create mode 100644 src/opencode/types/shared/voice_web_rtc_transport.py
create mode 100644 src/opencode/types/shared/voice_web_rtc_transport_input.py
create mode 100644 src/opencode/types/shared/voice_web_socket_transport.py
create mode 100644 src/opencode/types/shared/voice_web_socket_transport_input.py
create mode 100644 src/opencode/types/shared/web_search_id.py
create mode 100644 src/opencode/types/shared/web_search_provider.py
create mode 100644 src/opencode/types/shared/web_search_response.py
create mode 100644 src/opencode/types/shared/web_search_result.py
create mode 100644 src/opencode/types/shared/websearch_updated_event.py
create mode 100644 src/opencode/types/shared/workspace_id.py
create mode 100644 src/opencode/types/shared/worktree_directory.py
create mode 100644 src/opencode/types/shared/worktree_info.py
create mode 100644 src/opencode/types/shared/worktree_list.py
create mode 100644 src/opencode/types/shared/worktree_resolved_event.py
create mode 100644 src/opencode/types/shared/worktree_updated_event.py
create mode 100644 src/opencode/types/shell_create_params.py
create mode 100644 src/opencode/types/shell_create_response.py
create mode 100644 src/opencode/types/shell_get_params.py
create mode 100644 src/opencode/types/shell_get_response.py
create mode 100644 src/opencode/types/shell_list_params.py
create mode 100644 src/opencode/types/shell_list_response.py
create mode 100644 src/opencode/types/shell_output_params.py
create mode 100644 src/opencode/types/shell_output_response.py
create mode 100644 src/opencode/types/shell_remove_params.py
create mode 100644 src/opencode/types/skill_list_params.py
create mode 100644 src/opencode/types/skill_list_response.py
create mode 100644 src/opencode/types/vc_base_params.py
create mode 100644 src/opencode/types/vc_base_response.py
create mode 100644 src/opencode/types/vc_diff_params.py
create mode 100644 src/opencode/types/vc_diff_response.py
create mode 100644 src/opencode/types/vc_get_params.py
create mode 100644 src/opencode/types/vc_get_response.py
create mode 100644 src/opencode/types/vc_init_params.py
create mode 100644 src/opencode/types/vc_status_params.py
create mode 100644 src/opencode/types/vc_status_response.py
create mode 100644 src/opencode/types/vcs/__init__.py
create mode 100644 src/opencode/types/vcs/branch_list_params.py
create mode 100644 src/opencode/types/vcs/branch_list_response.py
create mode 100644 src/opencode/types/voice_session_params.py
create mode 100644 src/opencode/types/websearch_providers_params.py
create mode 100644 src/opencode/types/websearch_providers_response.py
create mode 100644 src/opencode/types/websearch_query_params.py
create mode 100644 src/opencode/types/websearch_query_response.py
create mode 100644 src/opencode/types/worktree_create_params.py
create mode 100644 src/opencode/types/worktree_list_params.py
create mode 100644 src/opencode/types/worktree_refresh_params.py
create mode 100644 src/opencode/types/worktree_remove_params.py
create mode 100644 stainless.yml
create mode 100644 tests/api_resources/__init__.py
create mode 100644 tests/api_resources/debug/__init__.py
create mode 100644 tests/api_resources/debug/test_location.py
create mode 100644 tests/api_resources/experimental/__init__.py
create mode 100644 tests/api_resources/experimental/test_persistent_pty.py
create mode 100644 tests/api_resources/integration/__init__.py
create mode 100644 tests/api_resources/integration/test_command.py
create mode 100644 tests/api_resources/integration/test_connect.py
create mode 100644 tests/api_resources/integration/test_oauth.py
create mode 100644 tests/api_resources/integration/test_wellknown.py
create mode 100644 tests/api_resources/mcp/__init__.py
create mode 100644 tests/api_resources/mcp/test_resource.py
create mode 100644 tests/api_resources/migration/__init__.py
create mode 100644 tests/api_resources/migration/test_v1.py
create mode 100644 tests/api_resources/permission/__init__.py
create mode 100644 tests/api_resources/permission/test_request.py
create mode 100644 tests/api_resources/permission/test_saved.py
create mode 100644 tests/api_resources/pty/__init__.py
create mode 100644 tests/api_resources/pty/test_connect.py
create mode 100644 tests/api_resources/session/__init__.py
create mode 100644 tests/api_resources/session/instructions/__init__.py
create mode 100644 tests/api_resources/session/instructions/test_entry.py
create mode 100644 tests/api_resources/session/test_form.py
create mode 100644 tests/api_resources/session/test_inbox.py
create mode 100644 tests/api_resources/session/test_message.py
create mode 100644 tests/api_resources/session/test_revert.py
create mode 100644 tests/api_resources/test_agent.py
create mode 100644 tests/api_resources/test_command.py
create mode 100644 tests/api_resources/test_config.py
create mode 100644 tests/api_resources/test_credential.py
create mode 100644 tests/api_resources/test_event.py
create mode 100644 tests/api_resources/test_file.py
create mode 100644 tests/api_resources/test_form.py
create mode 100644 tests/api_resources/test_generate.py
create mode 100644 tests/api_resources/test_integration.py
create mode 100644 tests/api_resources/test_location.py
create mode 100644 tests/api_resources/test_mcp.py
create mode 100644 tests/api_resources/test_message.py
create mode 100644 tests/api_resources/test_model.py
create mode 100644 tests/api_resources/test_permission.py
create mode 100644 tests/api_resources/test_plugin.py
create mode 100644 tests/api_resources/test_project.py
create mode 100644 tests/api_resources/test_provider.py
create mode 100644 tests/api_resources/test_pty.py
create mode 100644 tests/api_resources/test_reference.py
create mode 100644 tests/api_resources/test_rpc.py
create mode 100644 tests/api_resources/test_server.py
create mode 100644 tests/api_resources/test_session.py
create mode 100644 tests/api_resources/test_shell.py
create mode 100644 tests/api_resources/test_skill.py
create mode 100644 tests/api_resources/test_vcs.py
create mode 100644 tests/api_resources/test_voice.py
create mode 100644 tests/api_resources/test_websearch.py
create mode 100644 tests/api_resources/test_worktree.py
create mode 100644 tests/api_resources/vcs/__init__.py
create mode 100644 tests/api_resources/vcs/test_branch.py
create mode 100644 tests/sample_file.txt
create mode 100644 tests/test_agent_recipes.py
create mode 100644 tests/test_extract_files.py
create mode 100644 tests/test_files.py
create mode 100644 tests/test_qs.py
create mode 100644 tests/test_required_args.py
create mode 100644 tests/test_transform.py
create mode 100644 tests/test_utils/test_datetime_parse.py
create mode 100644 tests/test_utils/test_json.py
create mode 100644 tests/test_utils/test_path.py
create mode 100644 tests/test_utils/test_proxy.py
create mode 100644 tests/test_utils/test_typing.py
create mode 100644 tests/utils.py
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8d64a6f..dab74f3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,107 +1,85 @@
name: CI
-
on:
+ push:
+ branches:
+ - '**'
+ - '!integrated/**'
+ - '!stl-preview-head/**'
+ - '!stl-preview-base/**'
+ - '!generated'
+ - '!codegen/**'
+ - 'codegen/stl/**'
pull_request:
- workflow_dispatch:
- workflow_call: # the release workflow runs CI before publishing
-
-permissions:
- contents: read
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
+ branches-ignore:
+ - 'stl-preview-head/**'
+ - 'stl-preview-base/**'
jobs:
lint:
- name: Lint, types, generated code
+ timeout-minutes: 10
+ name: lint
runs-on: ubuntu-latest
+ if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
steps:
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
- with:
- enable-cache: true
- - run: uv sync --locked
- - run: uv run ruff check .
- - run: uv run ruff format --check .
- - run: uv run pyright
- - run: uv run mypy
- - name: Generated code matches spec/contract.json
- run: uv run python -m codegen --check
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- test:
- name: Test (Python ${{ matrix.python }}, ${{ matrix.os }})
- runs-on: ${{ matrix.os }}
- strategy:
- fail-fast: false
- matrix:
- os: [ubuntu-latest]
- python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
- include:
- - os: macos-latest
- python: "3.13"
- - os: windows-latest
- python: "3.13"
- steps:
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
- with:
- enable-cache: true
- python-version: ${{ matrix.python }}
- - run: uv sync --locked
- - run: uv run pytest -m "not live" --cov --cov-report=term-missing
+ - name: Install Rye
+ run: |
+ curl -sSf https://rye.astral.sh/get | bash
+ echo "$HOME/.rye/shims" >> $GITHUB_PATH
+ env:
+ RYE_VERSION: '0.44.0'
+ RYE_INSTALL_OPTION: '--yes'
- lowest:
- name: Test with lowest supported dependencies
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
- with:
- python-version: "3.10"
- - run: >-
- uv run --isolated --resolution lowest-direct --with-editable '.[pty,aiohttp]'
- --with pytest --with respx --with trio
- pytest -m "not live" -p no:cacheprovider
+ - name: Install dependencies
+ run: rye sync --all-features
- live:
- name: Live server
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
- with:
- enable-cache: true
- - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
- with:
- node-version: 22
- # The OpenCode version is pinned by hash in .github/live/package-lock.json.
- - run: npm ci --prefix .github/live
- - run: echo "$GITHUB_WORKSPACE/.github/live/node_modules/.bin" >> "$GITHUB_PATH"
- - run: opencode --version
- - run: uv sync --locked
- - run: uv run pytest -m live -v
+ - name: Run lints
+ run: ./scripts/lint
build:
- name: Build distributions
+ if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
+ timeout-minutes: 10
+ name: build
+ permissions:
+ contents: read
+ id-token: write
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
- - run: uv build
- - run: uvx twine check --strict dist/*
- - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
- with:
- name: dist
- path: dist/
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+
+ - name: Install Rye
+ run: |
+ curl -sSf https://rye.astral.sh/get | bash
+ echo "$HOME/.rye/shims" >> $GITHUB_PATH
+ env:
+ RYE_VERSION: '0.44.0'
+ RYE_INSTALL_OPTION: '--yes'
+
+ - name: Install dependencies
+ run: rye sync --all-features
+
+ - name: Run build
+ run: rye build
- fuzz:
- name: Fuzz SSE decoder
+ test:
+ timeout-minutes: 10
+ name: test
runs-on: ubuntu-latest
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- with:
- python-version: "3.12"
- - run: uv sync --locked
- - run: uv run --with atheris python fuzz/fuzz_sse.py -max_total_time=60
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+
+ - name: Install Rye
+ run: |
+ curl -sSf https://rye.astral.sh/get | bash
+ echo "$HOME/.rye/shims" >> $GITHUB_PATH
+ env:
+ RYE_VERSION: '0.44.0'
+ RYE_INSTALL_OPTION: '--yes'
+
+ - name: Bootstrap
+ run: ./scripts/bootstrap
+
+ - name: Run tests
+ run: ./scripts/test
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
new file mode 100644
index 0000000..52fc2f8
--- /dev/null
+++ b/.github/workflows/publish-pypi.yml
@@ -0,0 +1,31 @@
+# This workflow is triggered when a GitHub release is created.
+# It can also be run manually to re-publish to PyPI in case it failed for some reason.
+# You can run this workflow by navigating to https://www.github.com//actions/workflows/publish-pypi.yml
+name: Publish PyPI
+on:
+ workflow_dispatch:
+
+ release:
+ types: [published]
+
+jobs:
+ publish:
+ name: publish
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+
+ - name: Install Rye
+ run: |
+ curl -sSf https://rye.astral.sh/get | bash
+ echo "$HOME/.rye/shims" >> $GITHUB_PATH
+ env:
+ RYE_VERSION: '0.44.0'
+ RYE_INSTALL_OPTION: '--yes'
+
+ - name: Publish to PyPI
+ run: |
+ bash ./bin/publish-pypi
+ env:
+ PYPI_TOKEN: ${{ secrets.OPENCODE_PYPI_TOKEN || secrets.PYPI_TOKEN }}
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
new file mode 100644
index 0000000..2047f81
--- /dev/null
+++ b/.github/workflows/release-doctor.yml
@@ -0,0 +1,21 @@
+name: Release Doctor
+on:
+ pull_request:
+ branches:
+ - main
+ workflow_dispatch:
+
+jobs:
+ release_doctor:
+ name: release doctor
+ runs-on: ubuntu-latest
+ if: github.repository == '' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
+
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+
+ - name: Check release environment
+ run: |
+ bash ./bin/check-release-environment
+ env:
+ PYPI_TOKEN: ${{ secrets.OPENCODE_PYPI_TOKEN || secrets.PYPI_TOKEN }}
diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml
new file mode 100644
index 0000000..fd4c75f
--- /dev/null
+++ b/.github/workflows/sdk.yml
@@ -0,0 +1,31 @@
+name: sdk
+
+on:
+ push:
+ branches: [main]
+ paths: [openapi.json, stainless.yml]
+ schedule:
+ - cron: "0 6 * * *"
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ regenerate:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write # push ferrule/regenerate and the generated branch
+ pull-requests: write # open the PR and label it
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ fetch-depth: 0 # the previous release and the generated branch
+ persist-credentials: false
+ - uses: shrijayan/ferrule/action@0000000000000000000000000000000000000000
+ with:
+ spec: openapi.json
+ config: stainless.yml
+ verify: true # fails on an unlabeled breaking change
+ open-pr: true # labels major / minor / patch
+ github-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..a4fc8ec
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,9 @@
+# Using opencode 0.0.1
+
+Instructions for coding agents writing code that uses the `opencode` package.
+
+- Read `SKILL.md` next to this file first: install, authentication (`OPENCODE_PASSWORD`), one example per resource and error handling, for this exact version.
+- Look up method signatures and types in `api.md` (https://github.com//blob/main/api.md); every request and response type is importable from `opencode.types`.
+- Construct one `Opencode` (or `AsyncOpencode`) and reuse it; do not pass secrets in code.
+- Catch `opencode.APIError` subclasses rather than `httpx` errors; retries and timeouts are already built in.
+- Do not edit generated files of the SDK; they are regenerated from the API spec.
diff --git a/api.md b/api.md
index 7073153..cbec1c8 100644
--- a/api.md
+++ b/api.md
@@ -1,372 +1,973 @@
-# API reference
+# Shared Types
+
+```python
+from opencode.types import (
+ AbsolutePath,
+ AgentColor,
+ AgentID,
+ AgentInfo,
+ AgentUpdatedEvent,
+ CommandInfo,
+ CommandUpdatedEvent,
+ ConfigAgent,
+ ConfigCommand,
+ ConfigDirectory,
+ ConfigDocument,
+ ConfigEntry,
+ ConfigFormatterEntry,
+ ConfigInfo,
+ ConfigLSPServer,
+ ConfigModel,
+ ConfigModelCapabilities,
+ ConfigModelCost,
+ ConfigModelReasoning,
+ ConfigModelSettings,
+ ConfigPluginEntry,
+ ConfigProvider,
+ ConfigProviderSettings,
+ ConfigReferenceGit,
+ ConfigReferenceLocal,
+ ConfigShellOption,
+ ConfigUpdatedEvent,
+ ConfigWarming,
+ ConfigWebSearchInfo,
+ ConfigWorktree,
+ ConnectionCredentialInfo,
+ ConnectionEnvInfo,
+ ConnectionInfo,
+ ConnectionStatus,
+ CredentialEntry,
+ CredentialExternal,
+ CredentialID,
+ CredentialKey,
+ CredentialOAuth,
+ CredentialSwitchedEvent,
+ CredentialUpdatedEvent,
+ CredentialValue,
+ Event,
+ EventID,
+ EventSeq,
+ EventVersion,
+ EventVersion10,
+ FileDiffInfo,
+ FileSystemEntry,
+ FileSystemWrite,
+ FilesystemChangedEvent,
+ FormAnswer,
+ FormBooleanField,
+ FormCancelledEvent,
+ FormCreatedEvent,
+ FormDetail,
+ FormExternalField,
+ FormField,
+ FormFields,
+ FormFields1,
+ FormFields2,
+ FormFields3,
+ FormID,
+ FormInfo,
+ FormIntegerField,
+ FormMetadata,
+ FormMultiselectField,
+ FormNumberField,
+ FormOption,
+ FormRepliedEvent,
+ FormState,
+ FormStringField,
+ FormValue,
+ FormWhen,
+ GenerateTextResponse,
+ InstallationUpdateAvailableEvent,
+ InstallationUpdatedEvent,
+ InstructionEntryInfo,
+ InstructionEntryKey,
+ InstructionEntrySnapshot,
+ InstructionHash,
+ IntegrationAttempt,
+ IntegrationAttemptID,
+ IntegrationAttemptStatus,
+ IntegrationCommandAttempt,
+ IntegrationCommandAttemptStatus,
+ IntegrationCommandMethod,
+ IntegrationEnvMethod,
+ IntegrationExternalMethod,
+ IntegrationID,
+ IntegrationInfo,
+ IntegrationKeyMethod,
+ IntegrationMethod,
+ IntegrationMethodID,
+ IntegrationOAuthMethod,
+ IntegrationUpdatedEvent,
+ LocationPublicInfo,
+ LocationPublicRef,
+ LocationRef,
+ LocationShutdownEvent,
+ McpLocalConfig,
+ McpOAuthConfig,
+ McpProtocol,
+ McpRemoteConfig,
+ McpResource,
+ McpResourceCatalog,
+ McpResourceTemplate,
+ McpResourcesChangedEvent,
+ McpServer,
+ McpStatusChangedEvent,
+ McpStatusConnected,
+ McpStatusDisabled,
+ McpStatusFailed,
+ McpStatusNeedsAuth,
+ McpStatusPending,
+ ModelCacheFormat,
+ ModelCapabilities,
+ ModelCompatibility,
+ ModelCost,
+ ModelInfo,
+ ModelMaxTokensField,
+ ModelReasoningField,
+ ModelRef,
+ ModelSettings,
+ ModelUpdatedEvent,
+ ModelVariant,
+ ModelsDevRefreshedEvent,
+ MoneyUSD,
+ MoneyUSDPerMillionTokens,
+ PairingCode,
+ PairingSession,
+ PermissionAskedEvent,
+ PermissionEffect,
+ PermissionID,
+ PermissionRepliedEvent,
+ PermissionReply,
+ PermissionRequest,
+ PermissionRule,
+ PermissionRuleset,
+ PermissionSavedID,
+ PermissionSavedInfo,
+ PermissionSource,
+ PersistentPtyAddedEvent,
+ PersistentPtyHandoff,
+ PersistentPtyInfo,
+ PersistentPtyReadLines,
+ PersistentPtyReadResult,
+ PersistentPtyRemovedEvent,
+ PersistentPtySnapshot,
+ PluginFeatures,
+ PluginInfo,
+ PluginSource,
+ PluginState,
+ PluginUpdatedEvent,
+ Project,
+ ProjectCommands,
+ ProjectIcon,
+ ProjectID,
+ ProjectTime,
+ ProjectUpdatedEvent,
+ ProjectVcs,
+ PromptAgentAttachment,
+ PromptBase64,
+ PromptFileAttachment,
+ PromptFileSource,
+ PromptInputFileAttachment,
+ PromptInputSkillAttachment,
+ PromptMention,
+ PromptSkillAttachment,
+ ProviderCompaction,
+ ProviderCompatibility,
+ ProviderEncoding,
+ ProviderID,
+ ProviderInfo,
+ ProviderRequest,
+ ProviderSettings,
+ ProviderTransport,
+ ProviderUpdatedEvent,
+ Pty,
+ PtyCreatedEvent,
+ PtyDeletedEvent,
+ PtyExitedEvent,
+ PtyID,
+ PtyTicketConnectToken,
+ PtyUpdatedEvent,
+ ReferenceGitSource,
+ ReferenceInfo,
+ ReferenceLocalSource,
+ ReferenceSource,
+ ReferenceUpdatedEvent,
+ RelativePath,
+ RpcEvent,
+ RpcOutput,
+ ServerConnectedEvent,
+ ServerInfo,
+ SessionAgentSelectedEvent,
+ SessionArchivedEvent,
+ SessionCompactionDeltaEvent,
+ SessionCompactionEndedEvent,
+ SessionCompactionFailedEvent,
+ SessionCompactionStartedEvent,
+ SessionCreatedEvent,
+ SessionDeletedEvent,
+ SessionExecutionFailedEvent,
+ SessionExecutionInterruptedEvent,
+ SessionExecutionStartedEvent,
+ SessionExecutionSucceededEvent,
+ SessionForkBoundary,
+ SessionForkedEvent,
+ SessionGenerateResponse,
+ SessionID,
+ SessionID1,
+ SessionIdleEvent,
+ SessionInboxCancelledEvent,
+ SessionInboxCompaction,
+ SessionInboxCompactionPayload,
+ SessionInboxDeliveredEvent,
+ SessionInboxDelivery,
+ SessionInboxDeliveryChangedEvent,
+ SessionInboxEnqueuedEvent,
+ SessionInboxInfo,
+ SessionInboxItem,
+ SessionInboxMove,
+ SessionInboxMovePayload,
+ SessionInboxMovePayload1,
+ SessionInboxSynthetic,
+ SessionInboxSyntheticPayload,
+ SessionInboxUser,
+ SessionInboxUserPayload,
+ SessionInfo,
+ SessionInstructionsUpdatedEvent,
+ SessionInterruptResponse,
+ SessionMessageAgentSelected,
+ SessionMessageAssistant,
+ SessionMessageAssistantReasoning,
+ SessionMessageAssistantRetry,
+ SessionMessageAssistantText,
+ SessionMessageAssistantTool,
+ SessionMessageCompaction,
+ SessionMessageCompactionCompleted,
+ SessionMessageCompactionFailed,
+ SessionMessageCompactionRunning,
+ SessionMessageID,
+ SessionMessageIdle,
+ SessionMessageInfo,
+ SessionMessageLocationSwitched,
+ SessionMessageModelSelected,
+ SessionMessageNative,
+ SessionMessageNative1,
+ SessionMessageNative10,
+ SessionMessageNative11,
+ SessionMessageNative12,
+ SessionMessageNative2,
+ SessionMessageNative3,
+ SessionMessageNative4,
+ SessionMessageNative5,
+ SessionMessageNative6,
+ SessionMessageNative7,
+ SessionMessageNative8,
+ SessionMessageNative9,
+ SessionMessageShell,
+ SessionMessageSkill,
+ SessionMessageSynthetic,
+ SessionMessageSystem,
+ SessionMessageToolStateCompleted,
+ SessionMessageToolStateError,
+ SessionMessageToolStateRunning,
+ SessionMessageToolStateStreaming,
+ SessionMessageUser,
+ SessionMessagesResponse,
+ SessionMetadata,
+ SessionMetadataUpdatedEvent,
+ SessionModelSelectedEvent,
+ SessionMovedEvent,
+ SessionPermissionsEvent,
+ SessionReasoningDeltaEvent,
+ SessionReasoningEndedEvent,
+ SessionReasoningStartedEvent,
+ SessionRenamedEvent,
+ SessionRetryScheduledEvent,
+ SessionRevert,
+ SessionRevertClearedEvent,
+ SessionRevertCommittedEvent,
+ SessionRevertStagedEvent,
+ SessionShellEndedEvent,
+ SessionShellStartedEvent,
+ SessionSkillActivatedEvent,
+ SessionStatsActivity,
+ SessionStatsInfo,
+ SessionStatsModelUsage,
+ SessionStatsToolTotals,
+ SessionStatsToolUsage,
+ SessionStatsTools,
+ SessionStatus,
+ SessionStatusUpdated,
+ SessionStepEndedEvent,
+ SessionStepFailedEvent,
+ SessionStepStartedEvent,
+ SessionStepStreamedEvent,
+ SessionStructuredError,
+ SessionSyntheticEvent,
+ SessionTextDeltaEvent,
+ SessionTextEndedEvent,
+ SessionTextStartedEvent,
+ SessionToolCalledEvent,
+ SessionToolFailedEvent,
+ SessionToolInputDeltaEvent,
+ SessionToolInputEndedEvent,
+ SessionToolInputStartedEvent,
+ SessionToolProgressEvent,
+ SessionToolSuccessEvent,
+ SessionTransferData,
+ SessionUnarchivedEvent,
+ SessionUsageUpdatedEvent,
+ SessionViewedEvent,
+ SessionsCursor,
+ SessionsResponse,
+ ShellCreatedEvent,
+ ShellDeletedEvent,
+ ShellExitedEvent,
+ ShellID,
+ ShellInfo,
+ SkillID,
+ SkillInfo,
+ SkillName,
+ SkillUpdatedEvent,
+ SnapshotID,
+ TokenUsageInfo,
+ ToolContent,
+ ToolFileContent,
+ ToolTextContent,
+ TuiCommandExecuteEvent,
+ TuiPromptAppendEvent,
+ TuiSessionSelectEvent,
+ TuiToastShowEvent,
+ VcsBase,
+ VcsBranch,
+ VcsBranchList,
+ VcsBranchUpdatedEvent,
+ VcsFileStatus,
+ VcsInfo,
+ VcsMode,
+ VoiceSessionResponse,
+ VoiceTransport,
+ VoiceTransportInput,
+ VoiceWebRTCTransport,
+ VoiceWebRTCTransportInput,
+ VoiceWebSocketTransport,
+ VoiceWebSocketTransportInput,
+ WebSearchID,
+ WebSearchProvider,
+ WebSearchResponse,
+ WebSearchResult,
+ WebsearchUpdatedEvent,
+ WorkspaceID,
+ WorktreeDirectory,
+ WorktreeInfo,
+ WorktreeList,
+ WorktreeResolvedEvent,
+ WorktreeUpdatedEvent,
+)
+```
+
+# Agent
+
+Types:
+
+```python
+from opencode.types import AgentListResponse, AgentGetResponse
+```
+
+Methods:
+
+- client.agent.list(\*\*params) -> AgentListResponse
+- client.agent.get(agent_id, \*\*params) -> AgentGetResponse
+
+# Command
+
+Types:
+
+```python
+from opencode.types import CommandListResponse
+```
+
+Methods:
+
+- client.command.list(\*\*params) -> CommandListResponse
+
+# Config
+
+Types:
+
+```python
+from opencode.types import ConfigGetResponse, ConfigShellsResponse
+```
+
+Methods:
+
+- client.config.update(\*\*params) -> None
+- client.config.get(\*\*params) -> ConfigGetResponse
+- client.config.shells() -> ConfigShellsResponse
+
+# Credential
+
+Types:
-Generated from the OpenCode 2.1.2 contract. Every method exists on both `Opencode` and `AsyncOpencode`;
-async methods return awaitables, async list methods return `AsyncPaginator`, and async streams return
-`AsyncStream`. Types live in `opencode.types`.
+```python
+from opencode.types import CredentialCreateResponse, CredentialListResponse
+```
-## `client.agent`
+Methods:
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/agent` | `AgentListResponse` | List agents |
-| `get(agent_id)` | `GET /api/agent/:agentID` | `AgentGetResponse` | Get agent |
+- client.credential.create(\*\*params) -> CredentialCreateResponse
+- client.credential.update(credential_id, \*\*params) -> None
+- client.credential.list() -> CredentialListResponse
+- client.credential.activate(credential_id) -> None
+- client.credential.remove(credential_id) -> None
-## `client.command`
+# Debug
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/command` | `CommandListResponse` | List commands |
+## Location
-## `client.config`
+Types:
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `get()` | `GET /api/config` | `list[ConfigEntry]` | Get configuration |
-| `shells()` | `GET /api/config/shell` | `list[ConfigShellOption]` | List available shells |
-| `update()` | `PATCH /api/experimental/config` | `None` | Update global configuration *(experimental)* |
+```python
+from opencode.types.debug import LocationListResponse
+```
-## `client.credential`
+Methods:
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/credential` | `list[CredentialEntry]` | List credentials |
-| `create()` | `POST /api/credential` | `CredentialEntry` | Create credential |
-| `update(credential_id)` | `PATCH /api/credential/:credentialID` | `None` | Update credential |
-| `activate(credential_id)` | `POST /api/credential/:credentialID/activate` | `None` | Activate credential |
-| `remove(credential_id)` | `DELETE /api/credential/:credentialID` | `None` | Remove credential |
+- client.debug.location.list() -> LocationListResponse
+- client.debug.location.evict(\*\*params) -> None
-## `client.debug.location`
+# Event
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/debug/location` | `list[LocationPublicRef]` | List loaded locations |
-| `evict()` | `DELETE /api/debug/location` | `None` | Evict a loaded location |
+Methods:
-## `client.event`
+- client.event.subscribe() -> Event
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `subscribe()` | `GET /api/event` | `Stream[Event]` | Subscribe to events |
+# Experimental
-## `client.experimental.persistent_pty`
+## PersistentPty
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `read(session_id)` | `GET /api/experimental/session/:sessionID/terminal/read` | `PersistentPtyReadResult | None` | Read the session's most recently controlled terminal *(experimental)* |
-| `list(session_id)` | `GET /api/experimental/session/:sessionID/terminal` | `list[PersistentPtyInfo]` | *(experimental)* |
-| `create(session_id)` | `POST /api/experimental/session/:sessionID/terminal` | `PersistentPtyInfo` | *(experimental)* |
-| `shutdown()` | `POST /api/experimental/persistent-pty/shutdown` | `None` | *(experimental)* |
-| `handoff()` | `POST /api/experimental/persistent-pty/handoff` | `ExperimentalPersistentPtyHandoffResponse` | *(experimental)* |
-| `get(pty_id)` | `GET /api/experimental/persistent-pty/:ptyID` | `PersistentPtyInfo` | *(experimental)* |
-| `update(pty_id)` | `PUT /api/experimental/persistent-pty/:ptyID` | `PersistentPtyInfo` | *(experimental)* |
-| `snapshot(pty_id)` | `GET /api/experimental/persistent-pty/:ptyID/snapshot` | `PersistentPtySnapshot` | *(experimental)* |
-| `remove(pty_id)` | `DELETE /api/experimental/persistent-pty/:ptyID` | `None` | *(experimental)* |
-| `connect_token(pty_id)` | `POST /api/experimental/persistent-pty/:ptyID/connect-token` | `PtyTicketConnectToken` | *(experimental)* |
+Types:
-## `client.file`
+```python
+from opencode.types.experimental import (
+ PersistentPtyCreateResponse,
+ PersistentPtyUpdateResponse,
+ PersistentPtyListResponse,
+ PersistentPtyConnectTokenResponse,
+ PersistentPtyGetResponse,
+ PersistentPtyHandoffResponse,
+ PersistentPtyReadResponse,
+ PersistentPtySnapshotResponse,
+)
+```
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `read(path)` | `GET /api/fs/read/*` | `bytes` | Read file |
-| `list()` | `GET /api/fs/list` | `FileListResponse` | List directory |
-| `find()` | `GET /api/fs/find` | `FileFindResponse` | Find files |
-| `write()` | `POST /api/experimental/fs/write` | `FileWriteResponse` | Write file *(experimental)* |
+Methods:
-## `client.form`
+- client.experimental.persistent_pty.create(session_id, \*\*params) -> PersistentPtyCreateResponse
+- client.experimental.persistent_pty.update(pty_id, \*\*params) -> PersistentPtyUpdateResponse
+- client.experimental.persistent_pty.list(session_id) -> PersistentPtyListResponse
+- client.experimental.persistent_pty.connect_token(pty_id) -> PersistentPtyConnectTokenResponse
+- client.experimental.persistent_pty.get(pty_id) -> PersistentPtyGetResponse
+- client.experimental.persistent_pty.handoff() -> PersistentPtyHandoffResponse
+- client.experimental.persistent_pty.read(session_id, \*\*params) -> PersistentPtyReadResponse
+- client.experimental.persistent_pty.remove(pty_id) -> None
+- client.experimental.persistent_pty.shutdown() -> None
+- client.experimental.persistent_pty.snapshot(pty_id) -> PersistentPtySnapshotResponse
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/form` | `FormListResponse` | List pending forms |
+# File
-## `client.generate`
+Types:
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `text()` | `POST /api/experimental/generate` | `GenerateTextResponseData` | Generate text *(experimental)* |
+```python
+from opencode.types import FileListResponse, FileFindResponse, FileWriteResponse
+```
-## `client.integration`
+Methods:
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/integration` | `IntegrationListResponse` | List integrations |
-| `get(integration_id)` | `GET /api/integration/:integrationID` | `IntegrationGetResponse` | Get integration |
+- client.file.list(\*\*params) -> FileListResponse
+- client.file.find(\*\*params) -> FileFindResponse
+- client.file.read(path, \*\*params) -> None
+- client.file.write(\*\*params) -> FileWriteResponse
-## `client.integration.wellknown`
+# Form
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `add()` | `POST /api/experimental/integration/wellknown` | `None` | Add wellknown integration *(experimental)* |
+Types:
-## `client.integration.connect`
+```python
+from opencode.types import FormListResponse
+```
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `key(integration_id)` | `POST /api/integration/:integrationID/connect/key` | `None` | Connect with key |
-| `external(integration_id)` | `POST /api/integration/:integrationID/connect/external` | `None` | Connect with external credentials |
+Methods:
-## `client.integration.oauth`
+- client.form.list(\*\*params) -> FormListResponse
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `connect(integration_id)` | `POST /api/integration/:integrationID/connect/oauth` | `IntegrationOauthConnectResponse` | Begin OAuth connection |
-| `status(integration_id, attempt_id)` | `GET /api/integration/:integrationID/connect/oauth/:attemptID` | `IntegrationOauthStatusResponse` | Get OAuth attempt status |
-| `complete(integration_id, attempt_id)` | `POST /api/integration/:integrationID/connect/oauth/:attemptID/complete` | `None` | Complete OAuth connection |
-| `cancel(integration_id, attempt_id)` | `DELETE /api/integration/:integrationID/connect/oauth/:attemptID` | `None` | Cancel OAuth connection |
+# Generate
-## `client.integration.command`
+Methods:
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `connect(integration_id)` | `POST /api/integration/:integrationID/connect/command` | `IntegrationCommandConnectResponse` | Begin command connection |
-| `status(integration_id, attempt_id)` | `GET /api/integration/:integrationID/connect/command/:attemptID` | `IntegrationCommandStatusResponse` | Get command attempt status |
-| `cancel(integration_id, attempt_id)` | `DELETE /api/integration/:integrationID/connect/command/:attemptID` | `None` | Cancel command connection |
+- client.generate.text(\*\*params) -> GenerateTextResponse
-## `client.location`
+# Integration
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `get()` | `GET /api/location` | `LocationPublicInfo` | Get location |
-| `reload()` | `POST /api/location/reload` | `None` | Reload configuration |
+Types:
-## `client.mcp`
+```python
+from opencode.types import IntegrationListResponse, IntegrationGetResponse
+```
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/mcp` | `McpListResponse` | List MCP servers |
-| `add(server)` | `PUT /api/experimental/mcp/:server` | `None` | Add MCP server *(experimental)* |
-| `remove(server)` | `DELETE /api/experimental/mcp/:server` | `None` | Remove MCP server *(experimental)* |
-| `connect(server)` | `POST /api/experimental/mcp/:server/connect` | `None` | Connect MCP server *(experimental)* |
-| `disconnect(server)` | `POST /api/experimental/mcp/:server/disconnect` | `None` | Disconnect MCP server *(experimental)* |
+Methods:
-## `client.mcp.resource`
+- client.integration.list(\*\*params) -> IntegrationListResponse
+- client.integration.get(integration_id, \*\*params) -> IntegrationGetResponse
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `catalog()` | `GET /api/mcp/resource` | `McpResourceCatalogResponse` | List MCP resources |
+## Wellknown
-## `client.message`
+Methods:
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list(session_id)` | `GET /api/session/:sessionID/message` | `SyncCursorPage[SessionMessageInfo]` | Get session messages |
+- client.integration.wellknown.add(\*\*params) -> None
-## `client.migration.v1`
+## Connect
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `status()` | `GET /api/experimental/migration/v1` | `MigrationV1StatusResponseOption1 | MigrationV1StatusResponseOption2 | MigrationV1StatusResponseOption3` | Get V1 migration status *(experimental)* |
+Methods:
-## `client.model`
+- client.integration.connect.external(integration_id, \*\*params) -> None
+- client.integration.connect.key(integration_id, \*\*params) -> None
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/model` | `ModelListResponse` | List models |
-| `default()` | `GET /api/model/default` | `ModelDefaultResponse` | Get default model |
+## Oauth
-## `client.permission`
+Types:
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `create(session_id)` | `POST /api/session/:sessionID/permission` | `PermissionCreateResponse` | Create permission request |
-| `list(session_id)` | `GET /api/session/:sessionID/permission` | `list[PermissionRequest]` | List session permission requests |
-| `get(session_id, request_id)` | `GET /api/session/:sessionID/permission/:requestID` | `PermissionRequest` | Get permission request |
-| `reply(session_id, request_id)` | `POST /api/session/:sessionID/permission/:requestID/reply` | `None` | Reply to pending permission request |
+```python
+from opencode.types.integration import OauthConnectResponse, OauthStatusResponse
+```
-## `client.permission.request_`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/permission/request` | `PermissionRequestListResponse` | List pending permission requests |
-
-## `client.permission.saved`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/permission/saved` | `list[PermissionSavedInfo]` | List saved permissions |
-| `update(id)` | `PATCH /api/permission/saved/:id` | `PermissionSavedInfo` | Update saved permission |
-| `remove(id)` | `DELETE /api/permission/saved/:id` | `None` | Remove saved permission |
-
-## `client.plugin`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/plugin` | `PluginListResponse` | List plugins |
-| `check()` | `POST /api/plugin/check` | `PluginCheckResponse` | Check plugin updates |
-| `update()` | `POST /api/plugin/update` | `None` | Update plugins |
-
-## `client.project`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/project` | `list[Project]` | List projects |
-| `update(project_id)` | `PATCH /api/project/:projectID` | `Project` | Update project |
-
-## `client.provider`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/provider` | `ProviderListResponse` | List providers |
-| `get(provider_id)` | `GET /api/provider/:providerID` | `ProviderGetResponse` | Get provider |
-
-## `client.pty`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/pty` | `PtyListResponse` | List PTY sessions |
-| `create()` | `POST /api/pty` | `PtyCreateResponse` | Create PTY session |
-| `get(pty_id)` | `GET /api/pty/:ptyID` | `PtyGetResponse` | Get PTY session |
-| `update(pty_id)` | `PUT /api/pty/:ptyID` | `PtyUpdateResponse` | Update PTY session |
-| `remove(pty_id)` | `DELETE /api/pty/:ptyID` | `None` | Remove PTY session |
-
-## `client.pty.connect`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `token(pty_id)` | `POST /api/pty/:ptyID/connect-token` | `PtyConnectTokenResponse` | Create PTY WebSocket token |
-
-## `client.reference`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/reference` | `ReferenceListResponse` | List references |
-
-## `client.rpc`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `call(rpc_id, method)` | `POST /api/rpc/:rpcID/:method` | `RpcOutput` | Call a plugin RPC |
-
-## `client.server`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `info()` | `GET /api/info` | `ServerInfo` | Get server info |
-| `pair()` | `POST /api/pair` | `PairingCode` | Create pairing code |
-| `connect(code)` | `GET /auth/connect/:code` | `PairingSession` | Redeem pairing code |
-
-## `client.session`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/session` | `SyncCursorPage[SessionInfo]` | List sessions |
-| `stats()` | `GET /api/experimental/session/stats` | `SessionStatsInfo` | Get session statistics *(experimental)* |
-| `create()` | `POST /api/session` | `SessionInfo` | Create session |
-| `import_()` | `POST /api/experimental/session/import` | `SessionInfo` | Import session *(experimental)* |
-| `export(session_id)` | `GET /api/experimental/session/:sessionID/export` | `SessionTransferData` | Export session *(experimental)* |
-| `active()` | `GET /api/session/active` | `dict[str, Any]` | List active sessions |
-| `get(session_id)` | `GET /api/session/:sessionID` | `SessionInfo` | Get session |
-| `remove(session_id)` | `DELETE /api/session/:sessionID` | `None` | Delete session |
-| `fork(session_id)` | `POST /api/session/:sessionID/fork` | `SessionInfo` | Fork session |
-| `switch_agent(session_id)` | `POST /api/session/:sessionID/agent` | `None` | Switch session agent |
-| `switch_model(session_id)` | `POST /api/session/:sessionID/model` | `None` | Switch session model |
-| `update(session_id)` | `PATCH /api/session/:sessionID` | `None` | Update session |
-| `move(session_id)` | `POST /api/session/:sessionID/move` | `None` | Move session |
-| `prompt(session_id)` | `POST /api/session/:sessionID/prompt` | `SessionInboxUser` | Send message |
-| `command(session_id)` | `POST /api/session/:sessionID/command` | `None` | Run command |
-| `skill(session_id)` | `POST /api/experimental/session/:sessionID/skill` | `None` | Activate skill *(experimental)* |
-| `synthetic(session_id)` | `POST /api/session/:sessionID/synthetic` | `SessionInboxSynthetic` | Add synthetic message |
-| `shell(session_id)` | `POST /api/session/:sessionID/shell` | `None` | Run shell command |
-| `compact(session_id)` | `POST /api/session/:sessionID/compact` | `SessionInboxCompaction` | Compact session |
-| `wait(session_id)` | `POST /api/experimental/session/:sessionID/wait` | `None` | Wait for session *(experimental)* |
-| `context(session_id)` | `GET /api/session/:sessionID/context` | `list[SessionMessageInfo]` | Get session context |
-| `diff(session_id)` | `GET /api/session/:sessionID/diff` | `list[FileDiffInfo]` | Diff session turns |
-| `generate(session_id)` | `POST /api/session/:sessionID/generate` | `SessionGenerateResponseData` | Generate text from session context |
-| `log(session_id)` | `GET /api/experimental/session/:sessionID/log` | `Stream[SessionLogItem]` | Read the session log *(experimental)* |
-| `interrupt(session_id)` | `POST /api/session/:sessionID/interrupt` | `SessionInterruptResponse` | Interrupt session execution |
-| `background(session_id)` | `POST /api/session/:sessionID/background` | `None` | Background blocking session tools |
-| `environment(session_id)` | `PUT /api/session/:sessionID/environment` | `None` | Set session environment |
-| `view(session_id)` | `POST /api/session/:sessionID/view` | `None` | View session |
-
-## `client.session.revert`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `stage(session_id)` | `POST /api/session/:sessionID/revert/stage` | `SessionRevert` | Stage session revert |
-| `clear(session_id)` | `DELETE /api/session/:sessionID/revert` | `None` | Clear staged revert |
-| `commit(session_id)` | `POST /api/session/:sessionID/revert/commit` | `None` | Commit staged revert |
-
-## `client.session.inbox`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list(session_id)` | `GET /api/session/:sessionID/inbox` | `list[SessionInboxInfo]` | List session inbox |
-| `cancel(session_id, inbox_id)` | `DELETE /api/session/:sessionID/inbox/:inboxID` | `None` | Cancel inbox input |
-| `update(session_id, inbox_id)` | `PATCH /api/session/:sessionID/inbox/:inboxID` | `None` | Update inbox item |
-
-## `client.session.instructions.entry`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list(session_id)` | `GET /api/experimental/session/:sessionID/instructions/entries` | `list[InstructionEntryInfo]` | List instruction entries *(experimental)* |
-| `put(session_id, key)` | `PUT /api/experimental/session/:sessionID/instructions/entries/:key` | `None` | Put instruction entry *(experimental)* |
-| `remove(session_id, key)` | `DELETE /api/experimental/session/:sessionID/instructions/entries/:key` | `None` | Remove instruction entry *(experimental)* |
-
-## `client.session.message`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `get(session_id, message_id)` | `GET /api/session/:sessionID/message/:messageID` | `SessionMessageInfo` | Get session message |
-
-## `client.session.form`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list(session_id)` | `GET /api/session/:sessionID/form` | `list[FormInfo]` | List session forms |
-| `create(session_id)` | `POST /api/session/:sessionID/form` | `FormInfo` | Create session form |
-| `get(session_id, form_id)` | `GET /api/session/:sessionID/form/:formID` | `FormDetail` | Get session form |
-| `reply(session_id, form_id)` | `POST /api/session/:sessionID/form/:formID/reply` | `None` | Reply to form |
-| `cancel(session_id, form_id)` | `DELETE /api/session/:sessionID/form/:formID` | `None` | Cancel form |
-
-## `client.shell`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/shell` | `ShellListResponse` | List running shell commands |
-| `create()` | `POST /api/shell` | `ShellCreateResponse` | Run shell command |
-| `get(id)` | `GET /api/shell/:id` | `ShellGetResponse` | Get shell command |
-| `output(id)` | `GET /api/shell/:id/output` | `ShellOutputResponse` | Read shell output |
-| `remove(id)` | `DELETE /api/shell/:id` | `None` | Remove shell command |
-
-## `client.skill`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/skill` | `SkillListResponse` | List skills |
-
-## `client.vcs`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `init()` | `POST /api/vcs/init` | `None` | Initialize VCS repository |
-| `get()` | `GET /api/vcs` | `VcsGetResponse` | VCS info |
-| `base()` | `GET /api/vcs/base` | `VcsBaseResponse` | VCS review base |
-| `status()` | `GET /api/vcs/status` | `VcsStatusResponse` | VCS status |
-| `diff()` | `GET /api/vcs/diff` | `VcsDiffResponse` | VCS diff |
-
-## `client.vcs.branch`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/vcs/branch` | `VcsBranchListResponse` | VCS branches |
-
-## `client.voice`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `session()` | `POST /api/experimental/voice/session` | `VoiceSessionResponseData` | Create voice session *(experimental)* |
-
-## `client.websearch`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `providers()` | `GET /api/websearch/provider` | `WebsearchProvidersResponse` | List web search providers |
-| `query()` | `POST /api/websearch` | `WebsearchQueryResponse` | Search the web |
-
-## `client.worktree`
-
-| Method | HTTP | Returns | Summary |
-| --- | --- | --- | --- |
-| `list()` | `GET /api/worktree` | `WorktreeList` | List worktrees |
-| `create()` | `POST /api/worktree` | `WorktreeInfo` | Create worktree |
-| `remove()` | `DELETE /api/worktree` | `None` | Remove worktree |
-| `refresh()` | `POST /api/worktree/refresh` | `None` | Refresh worktrees |
+Methods:
+
+- client.integration.oauth.cancel(attempt_id, \*, integration_id, \*\*params) -> None
+- client.integration.oauth.complete(attempt_id, \*, integration_id, \*\*params) -> None
+- client.integration.oauth.connect(integration_id, \*\*params) -> OauthConnectResponse
+- client.integration.oauth.status(attempt_id, \*, integration_id, \*\*params) -> OauthStatusResponse
+
+## Command
+
+Types:
+
+```python
+from opencode.types.integration import CommandConnectResponse, CommandStatusResponse
+```
+
+Methods:
+
+- client.integration.command.cancel(attempt_id, \*, integration_id, \*\*params) -> None
+- client.integration.command.connect(integration_id, \*\*params) -> CommandConnectResponse
+- client.integration.command.status(attempt_id, \*, integration_id, \*\*params) -> CommandStatusResponse
+
+# Location
+
+Methods:
+
+- client.location.get(\*\*params) -> LocationPublicInfo
+- client.location.reload() -> None
+
+# Mcp
+
+Types:
+
+```python
+from opencode.types import McpListResponse
+```
+
+Methods:
+
+- client.mcp.list(\*\*params) -> McpListResponse
+- client.mcp.add(server, \*\*params) -> None
+- client.mcp.connect(server, \*\*params) -> None
+- client.mcp.disconnect(server, \*\*params) -> None
+- client.mcp.remove(server, \*\*params) -> None
+
+## Resource
+
+Types:
+
+```python
+from opencode.types.mcp import ResourceCatalogResponse
+```
+
+Methods:
+
+- client.mcp.resource.catalog(\*\*params) -> ResourceCatalogResponse
+
+# Message
+
+Methods:
+
+- client.message.list(session_id, \*\*params) -> SessionMessageInfo
+
+# Migration
+
+## V1
+
+Types:
+
+```python
+from opencode.types.migration import V1StatusResponse
+```
+
+Methods:
+
+- client.migration.v1.status() -> V1StatusResponse
+
+# Model
+
+Types:
+
+```python
+from opencode.types import ModelListResponse, ModelDefaultResponse
+```
+
+Methods:
+
+- client.model.list(\*\*params) -> ModelListResponse
+- client.model.default(\*\*params) -> ModelDefaultResponse
+
+# Permission
+
+Types:
+
+```python
+from opencode.types import PermissionCreateResponse, PermissionListResponse, PermissionGetResponse
+```
+
+Methods:
+
+- client.permission.create(session_id, \*\*params) -> PermissionCreateResponse
+- client.permission.list(session_id) -> PermissionListResponse
+- client.permission.get(request_id, \*, session_id) -> PermissionGetResponse
+- client.permission.reply(request_id, \*, session_id, \*\*params) -> None
+
+## Request
+
+Types:
+
+```python
+from opencode.types.permission import RequestListResponse
+```
+
+Methods:
+
+- client.permission.request.list(\*\*params) -> RequestListResponse
+
+## Saved
+
+Types:
+
+```python
+from opencode.types.permission import SavedUpdateResponse, SavedListResponse
+```
+
+Methods:
+
+- client.permission.saved.update(id, \*\*params) -> SavedUpdateResponse
+- client.permission.saved.list(\*\*params) -> SavedListResponse
+- client.permission.saved.remove(id) -> None
+
+# Plugin
+
+Types:
+
+```python
+from opencode.types import PluginListResponse, PluginCheckResponse
+```
+
+Methods:
+
+- client.plugin.update(\*\*params) -> None
+- client.plugin.list(\*\*params) -> PluginListResponse
+- client.plugin.check(\*\*params) -> PluginCheckResponse
+
+# Project
+
+Types:
+
+```python
+from opencode.types import ProjectListResponse
+```
+
+Methods:
+
+- client.project.update(project_id, \*\*params) -> Project
+- client.project.list(\*\*params) -> ProjectListResponse
+
+# Provider
+
+Types:
+
+```python
+from opencode.types import ProviderListResponse, ProviderGetResponse
+```
+
+Methods:
+
+- client.provider.list(\*\*params) -> ProviderListResponse
+- client.provider.get(provider_id, \*\*params) -> ProviderGetResponse
+
+# Pty
+
+Types:
+
+```python
+from opencode.types import PtyCreateResponse, PtyUpdateResponse, PtyListResponse, PtyGetResponse
+```
+
+Methods:
+
+- client.pty.create(\*\*params) -> PtyCreateResponse
+- client.pty.update(pty_id, \*\*params) -> PtyUpdateResponse
+- client.pty.list(\*\*params) -> PtyListResponse
+- client.pty.get(pty_id, \*\*params) -> PtyGetResponse
+- client.pty.remove(pty_id, \*\*params) -> None
+
+## Connect
+
+Types:
+
+```python
+from opencode.types.pty import ConnectTokenResponse
+```
+
+Methods:
+
+- client.pty.connect.token(pty_id, \*\*params) -> ConnectTokenResponse
+
+# Reference
+
+Types:
+
+```python
+from opencode.types import ReferenceListResponse
+```
+
+Methods:
+
+- client.reference.list(\*\*params) -> ReferenceListResponse
+
+# Rpc
+
+Methods:
+
+- client.rpc.call(method, \*, rpc_id, \*\*params) -> RpcOutput
+
+# Server
+
+Methods:
+
+- client.server.connect(code) -> PairingSession
+- client.server.info() -> ServerInfo
+- client.server.pair() -> PairingCode
+
+# Session
+
+Types:
+
+```python
+from opencode.types import (
+ SessionCreateResponse,
+ SessionActiveResponse,
+ SessionCompactResponse,
+ SessionContextResponse,
+ SessionDiffResponse,
+ SessionExportResponse,
+ SessionForkResponse,
+ SessionGetResponse,
+ SessionImportResponse,
+ SessionPromptResponse,
+ SessionStatsResponse,
+ SessionSyntheticResponse,
+)
+```
+
+Methods:
+
+- client.session.create(\*\*params) -> SessionCreateResponse
+- client.session.update(session_id, \*\*params) -> None
+- client.session.list(\*\*params) -> SessionInfo
+- client.session.active() -> SessionActiveResponse
+- client.session.background(session_id) -> None
+- client.session.command(session_id, \*\*params) -> None
+- client.session.compact(session_id, \*\*params) -> SessionCompactResponse
+- client.session.context(session_id) -> SessionContextResponse
+- client.session.diff(session_id, \*\*params) -> SessionDiffResponse
+- client.session.environment(session_id, \*\*params) -> None
+- client.session.export(session_id, \*\*params) -> SessionExportResponse
+- client.session.fork(session_id, \*\*params) -> SessionForkResponse
+- client.session.generate(session_id, \*\*params) -> SessionGenerateResponse
+- client.session.get(session_id) -> SessionGetResponse
+- client.session.import_(\*\*params) -> SessionImportResponse
+- client.session.interrupt(session_id, \*\*params) -> SessionInterruptResponse
+- client.session.log(session_id, \*\*params) -> None
+- client.session.move(session_id, \*\*params) -> None
+- client.session.prompt(session_id, \*\*params) -> SessionPromptResponse
+- client.session.remove(session_id) -> None
+- client.session.shell(session_id, \*\*params) -> None
+- client.session.skill(session_id, \*\*params) -> None
+- client.session.stats(\*\*params) -> SessionStatsResponse
+- client.session.switch_agent(session_id, \*\*params) -> None
+- client.session.switch_model(session_id, \*\*params) -> None
+- client.session.synthetic(session_id, \*\*params) -> SessionSyntheticResponse
+- client.session.view(session_id, \*\*params) -> None
+- client.session.wait(session_id) -> None
+
+## Revert
+
+Types:
+
+```python
+from opencode.types.session import RevertStageResponse
+```
+
+Methods:
+
+- client.session.revert.clear(session_id) -> None
+- client.session.revert.commit(session_id) -> None
+- client.session.revert.stage(session_id, \*\*params) -> RevertStageResponse
+
+## Inbox
+
+Types:
+
+```python
+from opencode.types.session import InboxListResponse
+```
+
+Methods:
+
+- client.session.inbox.update(inbox_id, \*, session_id, \*\*params) -> None
+- client.session.inbox.list(session_id) -> InboxListResponse
+- client.session.inbox.cancel(inbox_id, \*, session_id) -> None
+
+## Instructions
+
+### Entry
+
+Types:
+
+```python
+from opencode.types.session.instructions import EntryListResponse
+```
+
+Methods:
+
+- client.session.instructions.entry.list(session_id) -> EntryListResponse
+- client.session.instructions.entry.put(key, \*, session_id, \*\*params) -> None
+- client.session.instructions.entry.remove(key, \*, session_id) -> None
+
+## Message
+
+Types:
+
+```python
+from opencode.types.session import MessageGetResponse
+```
+
+Methods:
+
+- client.session.message.get(message_id, \*, session_id) -> MessageGetResponse
+
+## Form
+
+Types:
+
+```python
+from opencode.types.session import FormCreateResponse, FormListResponse, FormGetResponse
+```
+
+Methods:
+
+- client.session.form.create(session_id, \*\*params) -> FormCreateResponse
+- client.session.form.list(session_id) -> FormListResponse
+- client.session.form.cancel(form_id, \*, session_id, \*\*params) -> None
+- client.session.form.get(form_id, \*, session_id) -> FormGetResponse
+- client.session.form.reply(form_id, \*, session_id, \*\*params) -> None
+
+# Shell
+
+Types:
+
+```python
+from opencode.types import ShellCreateResponse, ShellListResponse, ShellGetResponse, ShellOutputResponse
+```
+
+Methods:
+
+- client.shell.create(\*\*params) -> ShellCreateResponse
+- client.shell.list(\*\*params) -> ShellListResponse
+- client.shell.get(id, \*\*params) -> ShellGetResponse
+- client.shell.output(id, \*\*params) -> ShellOutputResponse
+- client.shell.remove(id, \*\*params) -> None
+
+# Skill
+
+Types:
+
+```python
+from opencode.types import SkillListResponse
+```
+
+Methods:
+
+- client.skill.list(\*\*params) -> SkillListResponse
+
+# Vcs
+
+Types:
+
+```python
+from opencode.types import VcBaseResponse, VcDiffResponse, VcGetResponse, VcStatusResponse
+```
+
+Methods:
+
+- client.vcs.base(\*\*params) -> VcBaseResponse
+- client.vcs.diff(\*\*params) -> VcDiffResponse
+- client.vcs.get(\*\*params) -> VcGetResponse
+- client.vcs.init(\*\*params) -> None
+- client.vcs.status(\*\*params) -> VcStatusResponse
+
+## Branch
+
+Types:
+
+```python
+from opencode.types.vcs import BranchListResponse
+```
+
+Methods:
+
+- client.vcs.branch.list(\*\*params) -> BranchListResponse
+
+# Voice
+
+Methods:
+
+- client.voice.session(\*\*params) -> VoiceSessionResponse
+
+# Websearch
+
+Types:
+
+```python
+from opencode.types import WebsearchProvidersResponse, WebsearchQueryResponse
+```
+
+Methods:
+
+- client.websearch.providers(\*\*params) -> WebsearchProvidersResponse
+- client.websearch.query(\*\*params) -> WebsearchQueryResponse
+
+# Worktree
+
+Methods:
+
+- client.worktree.create(\*\*params) -> WorktreeInfo
+- client.worktree.list(\*\*params) -> WorktreeList
+- client.worktree.refresh(\*\*params) -> None
+- client.worktree.remove(\*\*params) -> None
diff --git a/examples/agent/agent_list.py b/examples/agent/agent_list.py
new file mode 100644
index 0000000..b230150
--- /dev/null
+++ b/examples/agent/agent_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+agent = client.agent.list()
+print(agent)
diff --git a/examples/agent/command_list.py b/examples/agent/command_list.py
new file mode 100644
index 0000000..d02563f
--- /dev/null
+++ b/examples/agent/command_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+command = client.command.list()
+print(command)
diff --git a/examples/agent/config_get.py b/examples/agent/config_get.py
new file mode 100644
index 0000000..8cebab5
--- /dev/null
+++ b/examples/agent/config_get.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+config = client.config.get()
+print(config)
diff --git a/examples/agent/credential_list.py b/examples/agent/credential_list.py
new file mode 100644
index 0000000..66e1595
--- /dev/null
+++ b/examples/agent/credential_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+credential = client.credential.list()
+print(credential)
diff --git a/examples/agent/debug_location_list.py b/examples/agent/debug_location_list.py
new file mode 100644
index 0000000..f1a7c26
--- /dev/null
+++ b/examples/agent/debug_location_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+location = client.debug.location.list()
+print(location)
diff --git a/examples/agent/experimental_persistent_pty_handoff.py b/examples/agent/experimental_persistent_pty_handoff.py
new file mode 100644
index 0000000..4ca39f2
--- /dev/null
+++ b/examples/agent/experimental_persistent_pty_handoff.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+persistent_pty = client.experimental.persistent_pty.handoff()
+print(persistent_pty)
diff --git a/examples/agent/file_list.py b/examples/agent/file_list.py
new file mode 100644
index 0000000..f8077bf
--- /dev/null
+++ b/examples/agent/file_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+file = client.file.list()
+print(file)
diff --git a/examples/agent/form_list.py b/examples/agent/form_list.py
new file mode 100644
index 0000000..ccc1351
--- /dev/null
+++ b/examples/agent/form_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+form = client.form.list()
+print(form)
diff --git a/examples/agent/generate_text.py b/examples/agent/generate_text.py
new file mode 100644
index 0000000..981af58
--- /dev/null
+++ b/examples/agent/generate_text.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+generate = client.generate.text(prompt="prompt")
+print(generate)
diff --git a/examples/agent/integration_command_cancel.py b/examples/agent/integration_command_cancel.py
new file mode 100644
index 0000000..3d982fa
--- /dev/null
+++ b/examples/agent/integration_command_cancel.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+command = client.integration.command.cancel(integration_id={}, attempt_id={})
+print(command)
diff --git a/examples/agent/integration_connect_external.py b/examples/agent/integration_connect_external.py
new file mode 100644
index 0000000..ce85d1e
--- /dev/null
+++ b/examples/agent/integration_connect_external.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+connect = client.integration.connect.external(integration_id={}, method_id={})
+print(connect)
diff --git a/examples/agent/integration_list.py b/examples/agent/integration_list.py
new file mode 100644
index 0000000..4d263e3
--- /dev/null
+++ b/examples/agent/integration_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+integration = client.integration.list()
+print(integration)
diff --git a/examples/agent/integration_oauth_cancel.py b/examples/agent/integration_oauth_cancel.py
new file mode 100644
index 0000000..d9f78a4
--- /dev/null
+++ b/examples/agent/integration_oauth_cancel.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+oauth = client.integration.oauth.cancel(integration_id={}, attempt_id={})
+print(oauth)
diff --git a/examples/agent/integration_wellknown_add.py b/examples/agent/integration_wellknown_add.py
new file mode 100644
index 0000000..8ea3291
--- /dev/null
+++ b/examples/agent/integration_wellknown_add.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+wellknown = client.integration.wellknown.add(url="url")
+print(wellknown)
diff --git a/examples/agent/location_get.py b/examples/agent/location_get.py
new file mode 100644
index 0000000..b16f47c
--- /dev/null
+++ b/examples/agent/location_get.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+location = client.location.get()
+print(location)
diff --git a/examples/agent/mcp_list.py b/examples/agent/mcp_list.py
new file mode 100644
index 0000000..6ebd7e8
--- /dev/null
+++ b/examples/agent/mcp_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+mcp = client.mcp.list()
+print(mcp)
diff --git a/examples/agent/mcp_resource_catalog.py b/examples/agent/mcp_resource_catalog.py
new file mode 100644
index 0000000..2231539
--- /dev/null
+++ b/examples/agent/mcp_resource_catalog.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+resource = client.mcp.resource.catalog()
+print(resource)
diff --git a/examples/agent/migration_v1_status.py b/examples/agent/migration_v1_status.py
new file mode 100644
index 0000000..628cb54
--- /dev/null
+++ b/examples/agent/migration_v1_status.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+v1 = client.migration.v1.status()
+print(v1)
diff --git a/examples/agent/model_list.py b/examples/agent/model_list.py
new file mode 100644
index 0000000..123af69
--- /dev/null
+++ b/examples/agent/model_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+model = client.model.list()
+print(model)
diff --git a/examples/agent/permission_create.py b/examples/agent/permission_create.py
new file mode 100644
index 0000000..75a90e4
--- /dev/null
+++ b/examples/agent/permission_create.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+permission = client.permission.create(session_id={}, action="action", resources=["string"])
+print(permission)
diff --git a/examples/agent/permission_request_list.py b/examples/agent/permission_request_list.py
new file mode 100644
index 0000000..4407a30
--- /dev/null
+++ b/examples/agent/permission_request_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+request = client.permission.request.list()
+print(request)
diff --git a/examples/agent/permission_saved_list.py b/examples/agent/permission_saved_list.py
new file mode 100644
index 0000000..f3313ad
--- /dev/null
+++ b/examples/agent/permission_saved_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+saved = client.permission.saved.list()
+print(saved)
diff --git a/examples/agent/plugin_list.py b/examples/agent/plugin_list.py
new file mode 100644
index 0000000..f2e4229
--- /dev/null
+++ b/examples/agent/plugin_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+plugin = client.plugin.list()
+print(plugin)
diff --git a/examples/agent/project_list.py b/examples/agent/project_list.py
new file mode 100644
index 0000000..0a45c87
--- /dev/null
+++ b/examples/agent/project_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+project = client.project.list()
+print(project)
diff --git a/examples/agent/provider_list.py b/examples/agent/provider_list.py
new file mode 100644
index 0000000..5605c1d
--- /dev/null
+++ b/examples/agent/provider_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+provider = client.provider.list()
+print(provider)
diff --git a/examples/agent/pty_connect_token.py b/examples/agent/pty_connect_token.py
new file mode 100644
index 0000000..e06aac6
--- /dev/null
+++ b/examples/agent/pty_connect_token.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+connect = client.pty.connect.token(pty_id={})
+print(connect)
diff --git a/examples/agent/pty_create.py b/examples/agent/pty_create.py
new file mode 100644
index 0000000..553d47e
--- /dev/null
+++ b/examples/agent/pty_create.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+pty = client.pty.create()
+print(pty)
diff --git a/examples/agent/reference_list.py b/examples/agent/reference_list.py
new file mode 100644
index 0000000..406ccf0
--- /dev/null
+++ b/examples/agent/reference_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+reference = client.reference.list()
+print(reference)
diff --git a/examples/agent/rpc_call.py b/examples/agent/rpc_call.py
new file mode 100644
index 0000000..eaa2133
--- /dev/null
+++ b/examples/agent/rpc_call.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+rpc = client.rpc.call(rpc_id="rpcID", method="method")
+print(rpc)
diff --git a/examples/agent/server_info.py b/examples/agent/server_info.py
new file mode 100644
index 0000000..c30b112
--- /dev/null
+++ b/examples/agent/server_info.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+server = client.server.info()
+print(server)
diff --git a/examples/agent/session_create.py b/examples/agent/session_create.py
new file mode 100644
index 0000000..564d440
--- /dev/null
+++ b/examples/agent/session_create.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+session = client.session.create()
+print(session)
diff --git a/examples/agent/session_form_create.py b/examples/agent/session_form_create.py
new file mode 100644
index 0000000..6050590
--- /dev/null
+++ b/examples/agent/session_form_create.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+form = client.session.form.create(session_id="sessionID", fields={}, title="title")
+print(form)
diff --git a/examples/agent/session_inbox_update.py b/examples/agent/session_inbox_update.py
new file mode 100644
index 0000000..7bea368
--- /dev/null
+++ b/examples/agent/session_inbox_update.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+inbox = client.session.inbox.update(session_id={}, inbox_id={}, delivery={})
+print(inbox)
diff --git a/examples/agent/session_instructions_entry_list.py b/examples/agent/session_instructions_entry_list.py
new file mode 100644
index 0000000..7841008
--- /dev/null
+++ b/examples/agent/session_instructions_entry_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+entry = client.session.instructions.entry.list(session_id={})
+print(entry)
diff --git a/examples/agent/session_message_get.py b/examples/agent/session_message_get.py
new file mode 100644
index 0000000..552bb58
--- /dev/null
+++ b/examples/agent/session_message_get.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+message = client.session.message.get(session_id={}, message_id={})
+print(message)
diff --git a/examples/agent/session_revert_clear.py b/examples/agent/session_revert_clear.py
new file mode 100644
index 0000000..f7471ce
--- /dev/null
+++ b/examples/agent/session_revert_clear.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+revert = client.session.revert.clear(session_id={})
+print(revert)
diff --git a/examples/agent/shell_list.py b/examples/agent/shell_list.py
new file mode 100644
index 0000000..88abe8b
--- /dev/null
+++ b/examples/agent/shell_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+shell = client.shell.list()
+print(shell)
diff --git a/examples/agent/skill_list.py b/examples/agent/skill_list.py
new file mode 100644
index 0000000..b4280ed
--- /dev/null
+++ b/examples/agent/skill_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+skill = client.skill.list()
+print(skill)
diff --git a/examples/agent/vcs_base.py b/examples/agent/vcs_base.py
new file mode 100644
index 0000000..e18523d
--- /dev/null
+++ b/examples/agent/vcs_base.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+vcs = client.vcs.base()
+print(vcs)
diff --git a/examples/agent/vcs_branch_list.py b/examples/agent/vcs_branch_list.py
new file mode 100644
index 0000000..a3c1441
--- /dev/null
+++ b/examples/agent/vcs_branch_list.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+branch = client.vcs.branch.list()
+print(branch)
diff --git a/examples/agent/voice_session.py b/examples/agent/voice_session.py
new file mode 100644
index 0000000..e462527
--- /dev/null
+++ b/examples/agent/voice_session.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+voice = client.voice.session(model="model", transport={})
+print(voice)
diff --git a/examples/agent/websearch_providers.py b/examples/agent/websearch_providers.py
new file mode 100644
index 0000000..8de1c2c
--- /dev/null
+++ b/examples/agent/websearch_providers.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+websearch = client.websearch.providers()
+print(websearch)
diff --git a/examples/agent/worktree_create.py b/examples/agent/worktree_create.py
new file mode 100644
index 0000000..9e53396
--- /dev/null
+++ b/examples/agent/worktree_create.py
@@ -0,0 +1,8 @@
+# Agent recipe generated by Ferrule from SKILL.md; tests/test_agent_recipes.py runs it.
+
+from opencode import Opencode
+
+client = Opencode()
+
+worktree = client.worktree.create(project_id={})
+print(worktree)
diff --git a/ferrule.lock b/ferrule.lock
new file mode 100644
index 0000000..80edae9
--- /dev/null
+++ b/ferrule.lock
@@ -0,0 +1,785 @@
+{
+ "config_sha256": "0365cf56a3497df641b31863dbec8dca95d197446d834125a05757c0f9559b0d",
+ "ferrule_version": "0.1.0",
+ "files": {
+ ".github/workflows/ci.yml": "5ac1be1c103477e7dcdb28d276ff14fb4ebf92650e92d6912e605a64df2dd27b",
+ ".github/workflows/publish-pypi.yml": "9d8ec64e69efcd63590be59b6aaa38669da06b8d1e473872fa82e6b572ea1626",
+ ".github/workflows/release-doctor.yml": "f1dcc10ac7b6f434cf8a6bcdfc3d069c6ff00f65f09e1274986816f38f05993d",
+ "AGENTS.md": "7f135189e8ab203aea9abecf018f905e395767cd38c24c2d22c3c1230c04c5e5",
+ "api.md": "e6f37671004a04c7440240bf3dd6fce0620f6d10a32cfc84f54cb1ccdfe160b0",
+ "examples/agent/agent_list.py": "8f50db0f50024f2b1b315f8f6c08f825445172fbef62b6350751498a2e1db9a9",
+ "examples/agent/command_list.py": "ebfe36957eed36b76d44ac4a867c847ad1f145b0a2f6959c4157f28617b15f7d",
+ "examples/agent/config_get.py": "f7765272fe31984e76ecc92db552ec7c6cf1e7d046c11d2a09f4a85af1f25a89",
+ "examples/agent/credential_list.py": "6d972dcaaaa72f47716bdc2179a47dd51089385bfdab40aec118a623bf9d2aae",
+ "examples/agent/debug_location_list.py": "c288adcd89a8b1008f013b3a5b79211e2701f85bc506e53443024883d279bced",
+ "examples/agent/experimental_persistent_pty_handoff.py": "726ecaa0fab7cee254f0c8bbd7f0df87d8513da5f27eaad010c0b94c9c30604f",
+ "examples/agent/file_list.py": "f66fd853bcbfd869127d3914f7eb6072df538ec2f90d367b8013acddc09d2ecd",
+ "examples/agent/form_list.py": "2e90ff861855be774507386175d7fa22bc453a5895e36394a6c2e8eb5ea45470",
+ "examples/agent/generate_text.py": "54f6ccbfba4dca6c733ec337d4a9232077ffc9bbe8d8e01330dfb2ee6fc1ca1b",
+ "examples/agent/integration_command_cancel.py": "a805b06e70c136a51a172d6496bd02e3ad50e00de0091cdb76169f8debc02217",
+ "examples/agent/integration_connect_external.py": "8bdd84ac847d3ab6cea2af901c0214a4179d7a69467d7c3131fe10962ee23a1c",
+ "examples/agent/integration_list.py": "9832387109077a8db224357b03abcf8e94543b0c3bef314129a8cf15a6ce8c63",
+ "examples/agent/integration_oauth_cancel.py": "2f4958831d813dd0445fc6a24b4b5aae953f7aec99e648c19d047a6672e8ff1d",
+ "examples/agent/integration_wellknown_add.py": "c6d5bba29b854b6bc1464daa3a6487cd27bdc8a1cb6a1eda004ab7fea6428826",
+ "examples/agent/location_get.py": "db2a0c118ea0bd020947aa661a6fb62007e9536be3960bdd2ebc4791bd07101e",
+ "examples/agent/mcp_list.py": "ef35aafe845ec46a4beaaf4d6930a3708a1e0396297947cc538aeb5cfef994c8",
+ "examples/agent/mcp_resource_catalog.py": "384f7216b8bb1005bb7a4ae67cf4fca179a03a4b16caf6a5851a544b6853faa9",
+ "examples/agent/migration_v1_status.py": "ac85bedc4e6278e150a8c19a79bea6fd8fc8c33652ea1cb78b69824484805326",
+ "examples/agent/model_list.py": "f57d438c91a9cd4debe75f97db7c5150129cb09d1b60e8a82b40e00835f46cc0",
+ "examples/agent/permission_create.py": "0d5922e0ff252b55379932593d59979ece3b496660117b4a10975961c748f518",
+ "examples/agent/permission_request_list.py": "8873887ce4a170c1dc504dd66770dceaf47de1e8d0854e1db695602c4d6331fd",
+ "examples/agent/permission_saved_list.py": "508030fd89b7d7c72d4778d47eb17275f040e406fb0c407cb1e0f6dc793cf31a",
+ "examples/agent/plugin_list.py": "2da03ca0e92c711ed8f356461e831f929c62cbbcc113dacce846d6c7cce8b052",
+ "examples/agent/project_list.py": "dc7f942e51170ee0ee4b49c817543135bbaf4f82422c5efdf08cbb60f6f34a68",
+ "examples/agent/provider_list.py": "de9b3661951a52004a03ff2a9053e856bebead8247326ceccdd5c051ad1ab333",
+ "examples/agent/pty_connect_token.py": "6b4876060338285842f250bdcc9e79f9128a2ced0bf4d325797061222678cd29",
+ "examples/agent/pty_create.py": "b7b03ddaf5a224fd450d84719369dbe7fb67c1b2bf35acf11ac7e90776d36edb",
+ "examples/agent/reference_list.py": "ad72fab168edce0e86ad301b0fff59c16958c273cd7fc1c4fcce1fceb3cd2e3c",
+ "examples/agent/rpc_call.py": "c89e2ad5e860682c6da65adbd5b76ce1087fab4abf598fff4f26e03288c50bc4",
+ "examples/agent/server_info.py": "0070e224f86aab73b5812ab1574a0d95cbae0a24a6805de26993cf33dd621241",
+ "examples/agent/session_create.py": "f1796fc1c024f01457eda899063930ead30d288a52e678a813675a6d4b604ed6",
+ "examples/agent/session_form_create.py": "505abd072ca25e35064e61ebe1f9081686e85849bab1acedd725896c253255b9",
+ "examples/agent/session_inbox_update.py": "e8a179772028584345dd4a3f1d348b3fadb55f56ea599c23c32026da486f27e4",
+ "examples/agent/session_instructions_entry_list.py": "bbd2e00d0816057795652f009e804a0b7f6ddc971af7b02d6f8560e3776723d0",
+ "examples/agent/session_message_get.py": "09da8ac11f73723ab6ab9d1ea3b54ff37db332483db570c865c5c27264bd0e12",
+ "examples/agent/session_revert_clear.py": "cdc8eab5957fbe4f44396cdd908e706e250f965f88d8251a48c008209300ff96",
+ "examples/agent/shell_list.py": "1752cd895257899fc387e09099349c6bb665e6ec02e9dd9e0499d678b1d3bd74",
+ "examples/agent/skill_list.py": "6a494c2a14749fb27d3cc902478d99686766f05b35ad19cba42259c5ac1c327d",
+ "examples/agent/vcs_base.py": "861ef19cdd183b27b1ef8ed3170ea41c416b58f8d75f02583c560158290b62b6",
+ "examples/agent/vcs_branch_list.py": "5d965ec1673776070a6ff0acfb41543a36da785c42df31b801b0554ff03c62b8",
+ "examples/agent/voice_session.py": "07ff5b3e962cd6eb27e0c852ca13a84f9af87f2628394f2100ada9e93e069899",
+ "examples/agent/websearch_providers.py": "30bb99ecdaf39b3c2112112a05c3b844aacfd24e298a4e6cdb40286d7954c3b2",
+ "examples/agent/worktree_create.py": "58d6fe4b42f4ecee2fa8e379abdeaa8a222c2b986c8acd07e83c49e05ada9fc3",
+ "llms.txt": "c9d8fa2e8580039de36b140ca06f2cb5558ff3d2b42e16effbd3a46ed682eef4",
+ "scripts/utils/ruffen-docs.py": "da4c57fe200f40d65dc9cefdfb1fa65245c407daa595b6522f43c44f14b271af",
+ "skills/opencode/SKILL.md": "5f52188520785747cc486ae9f2f094bbe00af72cdc06378c74632439ce3682f2",
+ "src/opencode/__init__.py": "9706cd16d41b78c7d20f1f32d04196bb7c11ffb5372dae6cf750eef7118db961",
+ "src/opencode/_agent/AGENTS.md": "7f135189e8ab203aea9abecf018f905e395767cd38c24c2d22c3c1230c04c5e5",
+ "src/opencode/_agent/SKILL.md": "5f52188520785747cc486ae9f2f094bbe00af72cdc06378c74632439ce3682f2",
+ "src/opencode/_agent/llms.txt": "c9d8fa2e8580039de36b140ca06f2cb5558ff3d2b42e16effbd3a46ed682eef4",
+ "src/opencode/_base_client.py": "32888441549574c13267ece8bfbc34394bfb41e6bef39633207fa8381805aae7",
+ "src/opencode/_client.py": "2b9828dc575d168465420bb6180ee9fa0c3d68eb2d62c87b3db0abe5d5c0fe22",
+ "src/opencode/_compat.py": "ffd82e41fcd89cbdc336db9d5f95bb4f671d4ac931f3d0790057e13d00f13149",
+ "src/opencode/_constants.py": "4b5e0f173c8df7e237d70895ed298894be466b430b1f176f7a022719d5c7eed3",
+ "src/opencode/_exceptions.py": "2d4b22b9988e3d5cda53564d3be707c509c76385414534b33f638c414a889b7d",
+ "src/opencode/_files.py": "49562a344d4bfb0626d46722c85f790422460b582c580aaf5ef4b2a2cfbfacda",
+ "src/opencode/_models.py": "25069c6a3ba112b956602a620daeecfe22b8ac756e6b7e8cf8491a189fcf9ab5",
+ "src/opencode/_qs.py": "6527d0bfa6ab1810685a5732eb4ccad7398f1362237b48bc93329d9e8c565235",
+ "src/opencode/_resource.py": "a5ccd520bc2b1692444938511f0639a8f8ccad1487b3464bd3800c3bdb457d25",
+ "src/opencode/_response.py": "9c4173babe2e8b200aace647a0dc84b20408d4307773c667840fcedd3d5cda79",
+ "src/opencode/_streaming.py": "6f1b01286fa891541604ac720bcde1150472fedfe2647e81222acfaee8e73f3c",
+ "src/opencode/_types.py": "372c3019a5fd1a51b01b478ceb561678d51194cfa01c084790bca83bfd7f7869",
+ "src/opencode/_utils/__init__.py": "9d0abe8856b96314da5b249a2e281ab5ec39ac781347433be453589b89ab3b5b",
+ "src/opencode/_utils/_compat.py": "df7db8e9e0dc977a702fa916b04855b93e0092b77c819116f4b3d39b8eb9a219",
+ "src/opencode/_utils/_datetime_parse.py": "6c0053b3405ceab69b745be72305e312158bd794dc460599ffa5c64ea37cc549",
+ "src/opencode/_utils/_json.py": "6e5f79bae216c2049f5d7fa03f5b6b2bf94300fc09ba361f274e42c68d92102e",
+ "src/opencode/_utils/_logs.py": "c2272c3d75ff9c923a6005c2d26e6da07df584974ab6b4dfea469ab659d1b806",
+ "src/opencode/_utils/_path.py": "0e4dbde257afb895cfe5ee26db46110c36453e5ef37a05ef73d29bddd502c092",
+ "src/opencode/_utils/_proxy.py": "6a09678f6c814c3c865fd02493672b647ae5d74a2a46671e532d99a74d3c5c4b",
+ "src/opencode/_utils/_reflection.py": "6661a42204ff3eec1e772341aeb2866f1a16b64a72b49354d6e5384079e610c5",
+ "src/opencode/_utils/_resources_proxy.py": "aa5e4747fccee58d8d83d22c0b94addbf50d7cc78183010cf7e3dd43e3ceefec",
+ "src/opencode/_utils/_streams.py": "48c0bdd1d88515e729120ff38034556dd4778521088155628f8b5a0fe9e830b3",
+ "src/opencode/_utils/_sync.py": "1c19d9924067cf1b703997b4db5d82e04ca846fc614d5b53acb143cf6ff15428",
+ "src/opencode/_utils/_transform.py": "3630b39a77ea62bb008a450742283a37d41fb9355b2a2a6e3f7babf6670d17e1",
+ "src/opencode/_utils/_typing.py": "37fe4f3ee14db1aca06ed03f9e965df7c4953752d042f15328be9b9163c16465",
+ "src/opencode/_utils/_utils.py": "da73cecc3acf7b61800e92c244ce1b3504bc32eecb8c48821bf2c9d8000bea38",
+ "src/opencode/pagination.py": "938fe8d88193c475cf9b7934b2e50de690f2060754e4458dfdd6657875483339",
+ "src/opencode/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "src/opencode/resources/__init__.py": "d726664313307373400dc208debb419b7c59b91f759629a8a0b880bf4d0a110c",
+ "src/opencode/resources/agent.py": "ce008b84dae0db6c167c846b0a9bc2010f27fa2fbc072444e0210c77594d3600",
+ "src/opencode/resources/command.py": "71817de475ecff45179c8be3440307ddb9e76196f4b200057d0ccea1fbed5505",
+ "src/opencode/resources/config.py": "df61a96eca71052b06735ccd78a9d9c30bdf9663a25fd42b6e3cd21637177390",
+ "src/opencode/resources/credential.py": "a25612e84b8732fb208adf3b1f8831255ec6eb791321b67a2e86107f226a5a60",
+ "src/opencode/resources/debug/__init__.py": "4b259cbe61b2fece4ebf71e4287a286f8440020daf88219543a9c7dd3038ec82",
+ "src/opencode/resources/debug/debug.py": "3257e35af342049cbb2377d97a78136bb35793eb89b1cacfed4e019bf024c2bd",
+ "src/opencode/resources/debug/location.py": "314e488618b6b3aa5d8528edad62c5bc98a1b085c3fe94a8077847ce4e8a25b1",
+ "src/opencode/resources/event.py": "1726d900bda94f6a9dda4929787e96972b8d5e11492b9294c5be25e2c11af854",
+ "src/opencode/resources/experimental/__init__.py": "28feac464d3d9902214d3960dede255c9564614645757eae8faa7df956ab4877",
+ "src/opencode/resources/experimental/experimental.py": "30e7249ccce9f09e96fe456a89e6f8e603769bb7438f195a984c3824e20960c5",
+ "src/opencode/resources/experimental/persistent_pty.py": "0b38d2845d715266b51e9ce1051fbb271a724fdbb229c38295ef5ef1688a291e",
+ "src/opencode/resources/file.py": "d33d2de768514bec732c430628ba04715be7917ee417abe8da38fd7e22f239af",
+ "src/opencode/resources/form.py": "f10e411104e93a9ac2c57fd9e3b8c93610a8ccd873e98e5c87345f48c0534664",
+ "src/opencode/resources/generate.py": "d835b2e9cd29cd06402bb493245e161649f01151ae0075d66335feae3d68bb64",
+ "src/opencode/resources/integration/__init__.py": "0f3f50a21c60d150dfee890354599cec64af5220c7916a2000dd2daab0bfcd6d",
+ "src/opencode/resources/integration/command.py": "0509872d6e3026362446d4bd49512c4419e94bdf99f724d65368a678a278f5c7",
+ "src/opencode/resources/integration/connect.py": "634655e1240ee0c520289a470b62ba78908543c984af32cdd004ec5adff16887",
+ "src/opencode/resources/integration/integration.py": "071a34591d0a50c6c904b2076ee69e0f3e1a638319ea9d110cabf3791cc2915a",
+ "src/opencode/resources/integration/oauth.py": "03fc5844ac7e8b7b0e672826f36caa5243442d8f6fd8bd95f78f9f2a383e83b6",
+ "src/opencode/resources/integration/wellknown.py": "2adf443327e60c0dee86a25784a3fdcd22780e970468d753ea891e56eb2d2ef5",
+ "src/opencode/resources/location.py": "26d5e93f24f2e4e6737193572c49a3ed7b91ae75e2464196f0e3f3b94a49005e",
+ "src/opencode/resources/mcp/__init__.py": "6909d57eb83ae7e40888c5d59b5a1b16385dcb31eb41745bb5ed2a431d5c0959",
+ "src/opencode/resources/mcp/mcp.py": "ca0a3ce12c799155bf9f8d8468722336a50710241c238746558f8c5ba388f1ba",
+ "src/opencode/resources/mcp/resource.py": "77be8c8239069effdbdb1e1cdb394cf9d6fb73e0b432390665bd11e9ffed49ef",
+ "src/opencode/resources/message.py": "dede2607eaee1315c8397490b3c9e02743b0bf217070824d99b9944089cbb204",
+ "src/opencode/resources/migration/__init__.py": "4de52a3e69dc12cfa1f3c325d0e451b251117d689b1d2067adf8701d6262a82d",
+ "src/opencode/resources/migration/migration.py": "2351d44babd9ce815676e40d74acb870cea50fa86664766c561729e899264f56",
+ "src/opencode/resources/migration/v1.py": "054977ec18a7974e22f870f203652286406e3af93e4131dcd5b04ce8af764026",
+ "src/opencode/resources/model.py": "2ed756685b0a5967075474e428f82cdea42bb37067e8eb0985ad3afe970be705",
+ "src/opencode/resources/permission/__init__.py": "fb877b53463f24574016eb09ec406c43bcce5207e0ceb25c2ee6be02f755486b",
+ "src/opencode/resources/permission/permission.py": "9f8fd1266acd4291df37c462dfb722f170d32cd8696d7a1e53b5eec7af540180",
+ "src/opencode/resources/permission/request.py": "ddf2ebb3ef0eb186da16e91e65a4f7bdd43058f6595b780755d6e08b41e73f39",
+ "src/opencode/resources/permission/saved.py": "15bdd9803cf848b6c006a6513bcfadd5dc8c33245a243d7a835212d145609b54",
+ "src/opencode/resources/plugin.py": "adddbafb0b0aabc81959183ced02aa4e7975a99a1c3f46e9b9c687044086710c",
+ "src/opencode/resources/project.py": "c95e35f9109077334c69c810fb163209802680ab3c50aa700f530f2a7161f38b",
+ "src/opencode/resources/provider.py": "070779334044ad73e7292e2d12186e0af5ffc827283312c8a6f1d8e52997e5a3",
+ "src/opencode/resources/pty/__init__.py": "575979f97e8633490072e1f29781224ee52d1edc824059f168950fe6f1f68a60",
+ "src/opencode/resources/pty/connect.py": "a82f53401c234863a2c5b0b5d3a0938cb960a52352118c3f03724df144434c5c",
+ "src/opencode/resources/pty/pty.py": "7e4dbd36ff0ac71d35f83bd33c0e4c96677d62377726ad93a7338c3d8501e86d",
+ "src/opencode/resources/reference.py": "d0285cfa15c46152ce452478df31320ab3fb2fcc7d418099d918c82caa769295",
+ "src/opencode/resources/rpc.py": "f9dd500e3379befee9978c09bcd6dbad8059189f4f06682094c54dc46b57f886",
+ "src/opencode/resources/server.py": "cf422b830f84978a4bb7626c8ec130bfb65e8c49670de4c5f52a17547ddb306f",
+ "src/opencode/resources/session/__init__.py": "acc72ffc26560ac86e9c50583c07264fb140d4168afb9b535368eda32d2283ec",
+ "src/opencode/resources/session/form.py": "7255ffa6b2a2176ef93640035398b72b3e888ce2a17b9c845d994fd7d5ad6c9f",
+ "src/opencode/resources/session/inbox.py": "d6a4a27359b6f8edab6c45cf3989a4bea579c473007820074a9cc3f1c4a80585",
+ "src/opencode/resources/session/instructions/__init__.py": "0f5a6aead7a12d2a4f73046d763f3e65444201724cc069e6793e97a22e34c22d",
+ "src/opencode/resources/session/instructions/entry.py": "e5cc3064bd11fb2c2319a9a0abcc17b64b4128377b5dba70ae882367fad612c6",
+ "src/opencode/resources/session/instructions/instructions.py": "af2fca01a9c6ee019b23871a86c48aeb65a8ab10d8fa50ecaa87247fef4918bc",
+ "src/opencode/resources/session/message.py": "2b137d3057fc3fa823eef96741bbee821df4d301608f6bdb6594e01a0df36a44",
+ "src/opencode/resources/session/revert.py": "102115b4c796bcdd27f24606c942eba225b7692e91b41f60dd4895d8baca646c",
+ "src/opencode/resources/session/session.py": "8d80e75b248870d2e8793e6b8c54505488b17cf2a2d175970ba6287cf6f26976",
+ "src/opencode/resources/shell.py": "cdfaa416d7a57625c1ceeef94498ef74034a9e83b09283e78429ac5e5d07ab10",
+ "src/opencode/resources/skill.py": "9bd25b6a394f8b741d7b3d22bfdbdd8222e72569c3ae66be2f15aa03efdd7af2",
+ "src/opencode/resources/vcs/__init__.py": "abcb3cb291e4a2d846ccda54be7ec502bd72cec9d86826d29034127e828746b3",
+ "src/opencode/resources/vcs/branch.py": "d5c92150a4fba999411afb728425d5688236a41ad24597116e8b03933540ff18",
+ "src/opencode/resources/vcs/vcs.py": "ae8ef54582102103a637bc87b9c711e9e1d15bcc11971b924d7f4d39a418a0c7",
+ "src/opencode/resources/voice.py": "8718417b8a82e7f0d817361f91dc06bb555b89abcace2ddee57e9394b8f43af6",
+ "src/opencode/resources/websearch.py": "b9a6b5fd8cf602193b9e46efc18413b493a540deaf45908482dd2b161fc8148c",
+ "src/opencode/resources/worktree.py": "aa9431902c0edd7ae626f9b98aa14775155331cf3c0106e9ff5dfcd8675f0b29",
+ "src/opencode/types/__init__.py": "830b81c25ccc70ba22148541fe4d3a92543a34d6c2bc8a04ae261a08f4efbfe2",
+ "src/opencode/types/agent_get_params.py": "be8560402450e9c9688692b61277624c480cb073b79844b3255347e256f2786c",
+ "src/opencode/types/agent_get_response.py": "50ae1d324ae34e602f676251b3a04af01f0232131ddbc4709fb4ec6d604f47eb",
+ "src/opencode/types/agent_list_params.py": "1475b5da3c35ac99d1e962a94b7be2e1259ff62b6732ce22bcd71d71da4237c1",
+ "src/opencode/types/agent_list_response.py": "9054b9aeb6121aa0deaff64ccae6085a6412efaf7102452ed5137452a1fd8117",
+ "src/opencode/types/command_list_params.py": "022ac1a7cac6800063960f432862ae0e0051e2909d68c8a76c165401f88ec4f7",
+ "src/opencode/types/command_list_response.py": "d255eb2cff2175b7fe8d4767bf1998c711a29bbf107f6548f6925393f10ff407",
+ "src/opencode/types/config_get_params.py": "02f58377b8bfef4b1474bc081003a5bbd304e547941ac1286867de22b59a6460",
+ "src/opencode/types/config_get_response.py": "a2e5e233b18fe161999a509d87910af800d30ce42f986a6ae5916156eca9eafe",
+ "src/opencode/types/config_shells_response.py": "6077455be2e1e4f8f54232fc96adca92cf0ea2cdc19bc8d155b7111a0ffad103",
+ "src/opencode/types/config_update_params.py": "39f05b262ed97cea315b70e5597759b4b3802fa7c2a0264e5ba8f425805f2015",
+ "src/opencode/types/credential_create_params.py": "81bb399cc9c4598c286ef55ca072000e7948451976c02cc54e6839ee9c8d1dd0",
+ "src/opencode/types/credential_create_response.py": "817724d5e181d6a06ad4ebd9763256d07ff0e94ba235b1b62bf5e3b31c073d72",
+ "src/opencode/types/credential_list_response.py": "7ae0b6d38e1947cc095e1b18db24402c51a78400b3b83ce5a629dd3ff8b57d18",
+ "src/opencode/types/credential_update_params.py": "683a39154ddb37c073db77bc9b4217603e1bd65789fb9c085c064634fe7ae8bd",
+ "src/opencode/types/debug/__init__.py": "73b434d0b2478eb094c964ac2628e4f242b6d6b814fa8c3c4066d356bce791fa",
+ "src/opencode/types/debug/location_evict_params.py": "1baa10cad69cfa9227ca0013920695062270c378c678e8d22812c8512ae3ab66",
+ "src/opencode/types/debug/location_list_response.py": "79343c51a9b3c11c3891311aa1d2f4990af86acde1e07c75f2a3b645c01f3ff0",
+ "src/opencode/types/experimental/__init__.py": "3e9ef7d26f9fa162a9167c5808b1acf2739469284567b862dd5caf89b3ba2182",
+ "src/opencode/types/experimental/persistent_pty_connect_token_response.py": "77dad53707daa7523298fd82b66f1417e15563fae7bd26c3456026bd916890c9",
+ "src/opencode/types/experimental/persistent_pty_create_params.py": "236d282e5270c25459a4c10c196817165825d4a566a5f1a4599d53e5ff2c988e",
+ "src/opencode/types/experimental/persistent_pty_create_response.py": "b91cadc3f0b7c7edaedab695ee78ecc2b1abbb12f88facff98020e603d28a2d6",
+ "src/opencode/types/experimental/persistent_pty_get_response.py": "57e03a4ee7be623ed890324043a05ee384428edbf3dc6378399c9b173554fb81",
+ "src/opencode/types/experimental/persistent_pty_handoff_response.py": "dc578ce4f46ebbc22bd70088289372d200f9ceabe4588879ad57bde682e94e8f",
+ "src/opencode/types/experimental/persistent_pty_list_response.py": "5ff88115b0eec279109bcb3f8467d550c338b2675a354e8dab0c10dfbef49499",
+ "src/opencode/types/experimental/persistent_pty_read_params.py": "6830ea39823b0eab2c0d8b1c80933fdfc144bb37997202bdd6d6b7da4a1f8623",
+ "src/opencode/types/experimental/persistent_pty_read_response.py": "b5020ee4a7ac6847f195a7eac2021145e0f079b1738df4cbbdf60a5388d9612f",
+ "src/opencode/types/experimental/persistent_pty_snapshot_response.py": "01f6992750c7d9d9a8bdd1869ae21b67664aa467ff326aa380fd7db08e928103",
+ "src/opencode/types/experimental/persistent_pty_update_params.py": "533ed8468baec4147bdf171c8c262f0a0bb66de9324a333ac53bf81701400ee1",
+ "src/opencode/types/experimental/persistent_pty_update_response.py": "a5c02f4c53a6848cdfd39ee3b75f506bcc5bebe3f5764a543d741055175c6f69",
+ "src/opencode/types/file_find_params.py": "5d84fd583375a9d552ab631395ebd09f1a21bf5f02edc3e7744e592e1eacc800",
+ "src/opencode/types/file_find_response.py": "f04dbb6ba494ec28677c1c7dfa435039beca44af0be54e7a2eacea752d669bfe",
+ "src/opencode/types/file_list_params.py": "7b30be019b2b46b83344e8605bedf47ac8f9d7870079dd630abb349e70e8dfdf",
+ "src/opencode/types/file_list_response.py": "c58dcce980470ad6df7cb2beb948244c9fb8a5ac58ad2489b4d88e34a91c35ac",
+ "src/opencode/types/file_read_params.py": "9a20f5bc9cf79e4ec30011d1fc72b1e0fb07ac0e2a3f279a1a0ef46838831876",
+ "src/opencode/types/file_write_params.py": "2355bcd41b0472eb05e8b369e5b212a3be9c1a19210fe74c68621139928362e0",
+ "src/opencode/types/file_write_response.py": "b288457250a6f8b5ba265c34d5e48e2b6c0b05b16d59ae105004b7b2a12abf88",
+ "src/opencode/types/form_list_params.py": "bdb4b755d1a0f6749574e3440e4c911894d14f2be6522b07c3167711db0910ef",
+ "src/opencode/types/form_list_response.py": "e4568e238aa6fb2a6d4a737b169f3c7669f59b07605022fe2b07e276a946219b",
+ "src/opencode/types/generate_text_params.py": "1759b815153b451de050e10a659d4a35c2fcfa401f45c703e2538306fc26486f",
+ "src/opencode/types/integration/__init__.py": "4a86ae7cb6a8f39f127de1ac4093eaada8862d850e60977c64007efba650833a",
+ "src/opencode/types/integration/command_cancel_params.py": "8977a985ad00a36b77716679e1fbb7471b70e7057be2841f253551e653c244a0",
+ "src/opencode/types/integration/command_connect_params.py": "da1ca04b1c05382b9ac65ed434041917054ccdd58c0913edaebcdfc8ebe4b937",
+ "src/opencode/types/integration/command_connect_response.py": "c963b142d0e9db07bc8697ed216587c5df9f9800de89a2fc035cd3ce3893c8fb",
+ "src/opencode/types/integration/command_status_params.py": "d373985640757bedc6bf4fced49722a73868ccc8647876187c334b50465098aa",
+ "src/opencode/types/integration/command_status_response.py": "4340646c169ad48efac4d28e123c161de0940830553df4805ab92be5c6a18f54",
+ "src/opencode/types/integration/connect_external_params.py": "a952200ad85f47fecd99451b034765c16de15a3caa30cdea0e1426591c7de6bb",
+ "src/opencode/types/integration/connect_key_params.py": "502f68954fa5463821b9632293c3ccb0f6d3452bf89c3f52c7c8c4c131ad576c",
+ "src/opencode/types/integration/oauth_cancel_params.py": "47cae039fd2d5249481b3d182b8d46ae310a397a852761d9fba8f44b25c7aae4",
+ "src/opencode/types/integration/oauth_complete_params.py": "54833c60e31757c25b2926f7e433979374240633481fc6ffab2531e842bfe46d",
+ "src/opencode/types/integration/oauth_connect_params.py": "f61dccd23ae9d5a152092c60a735d7f98fce7fcd9b251d1be28d22e6b882e137",
+ "src/opencode/types/integration/oauth_connect_response.py": "131972837863cd1ed4930a45915468553d98782f6654652abd95a1b32454e56f",
+ "src/opencode/types/integration/oauth_status_params.py": "17e3e0c12cf62d78fdb2b30bba8aeb1bb920569938b93f98acf19c8f2b487a40",
+ "src/opencode/types/integration/oauth_status_response.py": "b575a8acf45a5746d115f520d21a11c8b4bf523d95303d36a00776bc09a71da1",
+ "src/opencode/types/integration/wellknown_add_params.py": "cc0bf256f439885c26c25f50eec147b3c423b79457fb025949279eeb1f000f2a",
+ "src/opencode/types/integration_get_params.py": "df84c8e9e1233f79993ccce1754063f3548db82201b14e5edab04b6271c341b0",
+ "src/opencode/types/integration_get_response.py": "219aaafdaf31c4c311492d3ccadd9aec0ad8d30716bbc3f4f941c76537971c40",
+ "src/opencode/types/integration_list_params.py": "8f4028ad8d669d3a8f73e0ffcb7ab31cde9dadcafb2d1e5840c2c4b6431d4bd8",
+ "src/opencode/types/integration_list_response.py": "85aea7af0c1edf2950097cbc888a1aa1dd43675c7cc7b97b2ff24867a1e6309a",
+ "src/opencode/types/location_get_params.py": "d74aa699fc561c4f9a11ca17bd92b1abffa8c7f928c1b40e57b5155f6f152b29",
+ "src/opencode/types/mcp/__init__.py": "9c7ed27a7b272d4757c074deae0d6d404bf4ff0bd15cc27325a01cf5c0c827c4",
+ "src/opencode/types/mcp/resource_catalog_params.py": "b064e5553111d38375b5dc76f06aaa69bb67d4759720a02d20c03985f2513a81",
+ "src/opencode/types/mcp/resource_catalog_response.py": "fd47b711fc25db917d9b5c09395e3d2c59b6e9c455864a1154a346ef8313df69",
+ "src/opencode/types/mcp_add_params.py": "1cdcd10ba820824fc1a4967bb48da17765bc1984f94c558b8cbc4bb940a09363",
+ "src/opencode/types/mcp_connect_params.py": "1facefaaff42fc95cadd883c5fbf2de3fe87386297bfe6f1125bc730bfa1eafe",
+ "src/opencode/types/mcp_disconnect_params.py": "eeb447b80718e4d2366597a37a9877572e4e50286ef0013f6fea96f36514a62c",
+ "src/opencode/types/mcp_list_params.py": "e0e3ae48719a34bb89b69f9540e05fcb5e78dc85eacb414cc0fe830ec45ca163",
+ "src/opencode/types/mcp_list_response.py": "d8c38c5820c508fce4c3679d090407e902f29d9c89f2cb1833bb9e97bc29fc79",
+ "src/opencode/types/mcp_remove_params.py": "74be70ad05c41e2bd2aa982a0a0e9ade347f6b9a156f69da374c027d4cefa55e",
+ "src/opencode/types/message_list_params.py": "6377f60a3e9fdf0dfb002846f25a16bae3b60f5e4f3e83f39f854b8bbdf47e46",
+ "src/opencode/types/migration/__init__.py": "fc218c501c7569cff8190f2f7d9fa7eb6c56aab2b3098e0c6f006a622427c08b",
+ "src/opencode/types/migration/v1_status_response.py": "3e1abb038cd8787b48f20aa4d78a383300084f8259db4be12942b77ecd322b9a",
+ "src/opencode/types/model_default_params.py": "05d003e2537942967ff37db6dafb0a911dd082bb7002a7fd49e7267d340209ce",
+ "src/opencode/types/model_default_response.py": "55de44e56e579797c852fe33817d96e370185e43be94a5e2db9267b222bd5648",
+ "src/opencode/types/model_list_params.py": "e08b4c429ecabc6d22090a7661e47d147b50cbcf4852a86704f7948be7bc3aa7",
+ "src/opencode/types/model_list_response.py": "8b35fa6f9ef1dc633ad709d9ca8872c5840fa274bdedcc79e69b52c9d4a8ad83",
+ "src/opencode/types/permission/__init__.py": "c7752acf943229a63c66031d35ee24bea1df643515995d11fef0947f97cdaf34",
+ "src/opencode/types/permission/request_list_params.py": "ffbf3d9b49cace4ea4dc78a751949acd2e04fe41168c934a88442becbf3d54c7",
+ "src/opencode/types/permission/request_list_response.py": "4acf7bd36662d6763c9bcdd63612cd00f7b8c09741f18555ada14119ddf9eaa5",
+ "src/opencode/types/permission/saved_list_params.py": "29ebdf1c7f2e4afc7f23bce5477f2eb0fa7cea84730a1f6d335cf0e15d154a24",
+ "src/opencode/types/permission/saved_list_response.py": "00f96b4a76f74a24284dd656cf4fd35a688f211b71c5bb2eae0682ca7e8a4ea0",
+ "src/opencode/types/permission/saved_update_params.py": "07625ad77994424d91372db2b2407f0704dc1a04882759edaeebfa1811fd2cdb",
+ "src/opencode/types/permission/saved_update_response.py": "0bffd0f9a218de2acc683d59045e669cc8979b4925298c7784ef5d96b88d123d",
+ "src/opencode/types/permission_create_params.py": "8cb5a23fae19dae675023a41b34c87cbea43806a6faf54aa32219c15da115e08",
+ "src/opencode/types/permission_create_response.py": "adc99dd9dda7b525653fcdd63b5cda17b402955abcd1c5c62b7828f5f9cf152c",
+ "src/opencode/types/permission_get_response.py": "db3d6e99551172d5af49b518baee77bbc5dc3d39d6a7c1a2c18bbce7705cbc29",
+ "src/opencode/types/permission_list_response.py": "1a8cb2133946c19c6cb0ccb86827d5e2bd5910c09ffe2b4826bc5cbfb970a49e",
+ "src/opencode/types/permission_reply_params.py": "1c4823e29099ca4bf1a6363d19e283ed120c1bf02a331a5bafb927374d51ba74",
+ "src/opencode/types/plugin_check_params.py": "f6aa54d096800e02472aaf07fbf0695fb5304ccf160d332852e7408c748c557f",
+ "src/opencode/types/plugin_check_response.py": "829b2a88e73530e37ca423a7146549c081f7f4d0d7ee477546d4912d1d452987",
+ "src/opencode/types/plugin_list_params.py": "21ac9dc0c1b7492b5e338fa89e4e3e6b7be337ad2f5dbf28a01607ffe8bd739e",
+ "src/opencode/types/plugin_list_response.py": "506fe36b897745f909aea72ebef2c7c59796844bbfda4f3db6d5efaed2357aaa",
+ "src/opencode/types/plugin_update_params.py": "8f7374c38c405d27be56cba93e542f1006814947b668b60b399187215f165384",
+ "src/opencode/types/project_list_params.py": "2940c408742c7bf50978730ef31a5489d256414c5825506907c2d918da039a07",
+ "src/opencode/types/project_list_response.py": "caafcf7f4943c258b79784762f29b38b97f24865bcfc60b2816f0a8c52558e77",
+ "src/opencode/types/project_update_params.py": "32bf1a3ca52f5cffbe173b8a12cbcac720db857c2a2695bdcc94054c9d39a92b",
+ "src/opencode/types/provider_get_params.py": "123ccf9c33d27df470e939ae22bf17bf2fd3b860f1ffb580403cc754e241ab3a",
+ "src/opencode/types/provider_get_response.py": "218baab99810490c03fa3ff9be2f3139e9cf817a5cc9c2d86195e5600b23b02c",
+ "src/opencode/types/provider_list_params.py": "afdfa108a723f5a5519dd2d876af419ce96bf57776f2af296c484dc9343c3301",
+ "src/opencode/types/provider_list_response.py": "29befcd24393e6215f37308ab8069873815952bccf66dc578e90e00952850828",
+ "src/opencode/types/pty/__init__.py": "0075c2a0da3ef0e3133ad0a67e96561d8104725c6ef924f06246d3daf23229b0",
+ "src/opencode/types/pty/connect_token_params.py": "9aec09a643bc5c2ba383a4f09464626bfcc8d98509d0a4efb6bf25f7bed71f3a",
+ "src/opencode/types/pty/connect_token_response.py": "7281ee5b6f37848f890b10de7a20fa9a7c05e08204dfc09e3b4cce38dd1344d1",
+ "src/opencode/types/pty_create_params.py": "7406dac021a155cda8c175152de6f242b5a7c3560c24acce99ebed18882b5cd7",
+ "src/opencode/types/pty_create_response.py": "cde06c2f0400e31e3d4f89cf5ac1ab0e8c70b813d65ce34633d08d1b9eba178c",
+ "src/opencode/types/pty_get_params.py": "6afb7e70a2e2070e5838b440d4f844d96270df69f61f3e338cf144429ae90ac2",
+ "src/opencode/types/pty_get_response.py": "c96c87b12c65a622659b3424075ac57b7dc65d8f5917029be1f4df522ff05d76",
+ "src/opencode/types/pty_list_params.py": "bfd64d5ba0c543bb0c30b3c2c2b90434d0216eb8a4eb9639230376cc526d3d8c",
+ "src/opencode/types/pty_list_response.py": "2ed18e1610c0d76b322b61c71563e39eaa320492ebb264eac050597da7c21945",
+ "src/opencode/types/pty_remove_params.py": "e441931df3cf7dce9b985e53ae90b8a083c766a166505e0f5e5091e934f256ed",
+ "src/opencode/types/pty_update_params.py": "c65bfff6e08b7f65326b1079d79b7f256a487d7f529892e575558e42cd09c581",
+ "src/opencode/types/pty_update_response.py": "312b9300dada875fd0a7d073061c3e2acafa52b342c94ef888f5f4259cc1b65d",
+ "src/opencode/types/reference_list_params.py": "50e4a47b39223fb0e4050f942891de626133211ea1a3a747668cc5ddf7d9e061",
+ "src/opencode/types/reference_list_response.py": "c894bef0432b919a193895dca66fde301ac1c399871f3b9a63b465b74db2e82b",
+ "src/opencode/types/rpc_call_params.py": "5e07a35dfb09fa706b5912360c15946fbd40cf3853dbb9af5ba3da13f10766db",
+ "src/opencode/types/session/__init__.py": "b5fd38a1f18ef5fd0ab2459c3695421e7abbc59c1cf469f3f001adf183b92611",
+ "src/opencode/types/session/form_cancel_params.py": "abb421bda795889519748aae780bf1cf2920a906a76a16999a4852e24a1eaa18",
+ "src/opencode/types/session/form_create_params.py": "0f0355e363dd54a01442eca9ec10e32688de0f38b36ee7b1eb48224053f29a2b",
+ "src/opencode/types/session/form_create_response.py": "241357507caa4cd7389e35733872efe36362fb2e089af496f7664034006bc2a7",
+ "src/opencode/types/session/form_get_response.py": "37a9def3001ac4dfcbdf2c0a342c2e55e50790428b3ce29e14b7125015aaf9c6",
+ "src/opencode/types/session/form_list_response.py": "b2e96159a416668f9330eb2957467be51bc0b504a9d5c77b0a8ebe710b4f14b7",
+ "src/opencode/types/session/form_reply_params.py": "376781129720221bb31d8a7bb37f0d0aebfb8b50ab89bdc6e00dc492f3ab7b6a",
+ "src/opencode/types/session/inbox_list_response.py": "3bd40fb8db352b15974d7d835f0d010677ba4bf77bfedfb835147913013c7902",
+ "src/opencode/types/session/inbox_update_params.py": "650c05c77c5a5f139a6b9435dd9b6fd83c09dc23da45bb3a86f8b4af2a5b7a84",
+ "src/opencode/types/session/instructions/__init__.py": "3483aac0cea6dee1c889af1ef0178657ba1b9f3d9f60d7f9cbbb8ca064387df6",
+ "src/opencode/types/session/instructions/entry_list_response.py": "41c5233bc8c6312aa8d7a433e60776b451cb3e397b30927a45cc3bf28b529649",
+ "src/opencode/types/session/instructions/entry_put_params.py": "7c5158106d40dad31128b2ed9ab5baf95447ff9b7426ccd71c99c2dee13cbf33",
+ "src/opencode/types/session/message_get_response.py": "cc826560d3f0370a41f1a4302a4cd92e20a2fe6230d775d274ce1bf2a069774e",
+ "src/opencode/types/session/revert_stage_params.py": "7517f0d130683f228fe7a4bc07350084c4acbec6b00c232b548b61a3b3b93a55",
+ "src/opencode/types/session/revert_stage_response.py": "6cacd29068d89203a5344ec2d3ef73b291e55d0d0cd73d2fb297df6cf0cd89cc",
+ "src/opencode/types/session_active_response.py": "7f59a9329fd5f7f85b8a998a560eb2eee09248edb100e556c6d446382bf7201a",
+ "src/opencode/types/session_command_params.py": "a8d1895135c4fe0f8ff076c1ae8230df9d9557da55d223d697ec56957f70b6da",
+ "src/opencode/types/session_compact_params.py": "2b50277e6ffa1b74c9199ff0b892ea3e083e64435e8bafb6bbfef89b2a546a37",
+ "src/opencode/types/session_compact_response.py": "253a83b4f4180302d373040e8d12785ed28ac41a9fd03819744801a1183c05ee",
+ "src/opencode/types/session_context_response.py": "3e38c5ef8159b21fef5eb92879e4cc61682a7407a607dd24cb5cb2ffbd1464b1",
+ "src/opencode/types/session_create_params.py": "9525aa09dc85f2954dc25c13b79d30379ff1011455bc9ef19b57fe77f694afa3",
+ "src/opencode/types/session_create_response.py": "ef466d0762a6aafa1916ff4a74edc88d495a9ab28165c17dc7f2e70e2ee093fb",
+ "src/opencode/types/session_diff_params.py": "f3d6c3f18b35fa4f025d069a809240652f808d973b9e8d169e672619f7e2f121",
+ "src/opencode/types/session_diff_response.py": "1aa2b8a8f0c195e4a959c8cc44ec401666cdf5516c7bd14ba390bae00e32f08c",
+ "src/opencode/types/session_environment_params.py": "a35911520e2c0b82a4c920eef3e2ba65b0bfd3455d5420add40f9e8984668354",
+ "src/opencode/types/session_export_params.py": "2296b68e545564b41105df5c9a7b891c228106f2a6380bc22ced70be81b65693",
+ "src/opencode/types/session_export_response.py": "00744c6540c2f52f3e60997a2481cd325d4681deb9b7aea742c4e770ac5911a6",
+ "src/opencode/types/session_fork_params.py": "ca7ee6b420dcaf50246609a34d407c30e717133f11db7965cd3893c1691494bc",
+ "src/opencode/types/session_fork_response.py": "10916090b35ff32d0a519bb294bb97838c48e5be066ec7cd2f49f7477d213e05",
+ "src/opencode/types/session_generate_params.py": "2d3d6eaaa87c229c39b8caf327c78bf4d12fcb7da6619357749f97730c622102",
+ "src/opencode/types/session_get_response.py": "f54d9c93c99436a4d00e3988baccc85e8acc9c2404104c30279b83c30aef57f0",
+ "src/opencode/types/session_import__params.py": "dee2d92a313a4a06635f87020f3dfff97bab4ffed24b4757d5263444265ca3fe",
+ "src/opencode/types/session_import__response.py": "bb78a3740f75253d6b29f4b2b21dd1cbc94882bdf359bacd7c3ec8f72f5df3fc",
+ "src/opencode/types/session_interrupt_params.py": "8a9349252a0aa18bbf7c4599d63d8d00dd624753eed7f4966365d02181718d94",
+ "src/opencode/types/session_list_params.py": "5dab22fbb7c461e3ba721ddc8508159b04384531d872b55384ef72bcdab58069",
+ "src/opencode/types/session_log_params.py": "d9442d22629ae55bbb2442d1bae0d7b86a0a5118a314a3d924416e79b96fc482",
+ "src/opencode/types/session_move_params.py": "3c6a91501779eb32e860358358e2c3702ac5b56583b85deedeb5cdb9ec029c35",
+ "src/opencode/types/session_prompt_params.py": "35bae1071c0ad9db4484af213bea46df5971761c21da8a4b74b48bf786619994",
+ "src/opencode/types/session_prompt_response.py": "f1cf4e75a6c0a7a804c1c24d8ab18dce6948e04153bd923926371bce9ac37346",
+ "src/opencode/types/session_shell_params.py": "ed4ebb44f2ce91fe821eff9730e25478db8b0036451646a0abfe458e7b11370d",
+ "src/opencode/types/session_skill_params.py": "18e26d1594cef3ee145742f85b184de6219148cc551ac7fef65f890a6d42c028",
+ "src/opencode/types/session_stats_params.py": "b811496ff06021a0fdb13ca6c3159a1e829b2fb985cf8be9b32a2569309b0fef",
+ "src/opencode/types/session_stats_response.py": "2d81a3401ea90c46eb4958000f69056fb274a748f88f707b0c5733300d8311a1",
+ "src/opencode/types/session_switch_agent_params.py": "8bd095ea38b570191139c65d468abcc2bde78ed565938f0a19d1fc56c6947328",
+ "src/opencode/types/session_switch_model_params.py": "c578e2baa2b041057338222b22519affde308abda7f1bb9e556eb534367e59bf",
+ "src/opencode/types/session_synthetic_params.py": "a98d79a87e192d25418fed0a13aca1591dccecd548b1c668a9a7077c7cdb191a",
+ "src/opencode/types/session_synthetic_response.py": "31edcf70ec3bb0734df267d06216db92a57801560a4d9395824986ca2b9855b5",
+ "src/opencode/types/session_update_params.py": "d764478bc1ea540f95357053339a1f0cf42db8fa7edb903c0a6df0d945ee3719",
+ "src/opencode/types/session_view_params.py": "f8c3de01a8752c1cd41270155ef8ddba34519538469ecb08a493b9838e297862",
+ "src/opencode/types/shared/__init__.py": "7499a0013c848f831de5ca14529dc97f15a10478934cd0f53c7e50ba95da24a0",
+ "src/opencode/types/shared/absolute_path.py": "b1218c627f0776ab990413feab99176032e7c46ef042c30eb35dbf43629b82e8",
+ "src/opencode/types/shared/agent_color.py": "f30ed503c618288e3a6b018c03835e6c19df1d1ae88c1f486b5caf68780f0983",
+ "src/opencode/types/shared/agent_id.py": "d1972e1178e8cf3eca6ac5a1a8b1710d0b709ba41a05ba2c0bbdc6eec3e03a9c",
+ "src/opencode/types/shared/agent_info.py": "8dd19036b07fb9ac108fe2b615fff663c0e10f6b805074732d070986204c7467",
+ "src/opencode/types/shared/agent_updated_event.py": "761bda7c9a08efdfa60966b8db668d81c5be5bcaace780e9a4c46359c4291997",
+ "src/opencode/types/shared/command_info.py": "f6b5f78c2cfa22809f61e64f14def025451b0522a03f8ccf6dbed9c13d981ceb",
+ "src/opencode/types/shared/command_updated_event.py": "a76fcd067541ebd050d01d98c8a064627dd78d0a62b44810c198bc88b13e0312",
+ "src/opencode/types/shared/config_agent.py": "063b2a2ab10a033576d040bc9d85f350784c076bfdb25e39879a78c454139cee",
+ "src/opencode/types/shared/config_command.py": "10ef9720dbd10fb97235ef949a771168b39e7b9e93583bf817ee10ff36c4ed4c",
+ "src/opencode/types/shared/config_directory.py": "3f0d80f356909234921eb0892b8a519c24bda2087052773d55b3401161783542",
+ "src/opencode/types/shared/config_document.py": "a4f9b67732bb4fd0143246ad78af10c93034d68e176bab08afca771f944e8f1f",
+ "src/opencode/types/shared/config_entry.py": "ca40a0985937f9fe0d3019187437fd5573daa014e161b8a08b5478ff170b13be",
+ "src/opencode/types/shared/config_formatter_entry.py": "0645b0aa604093adcbd7db5830812529f22f839547e56dfa412c795f0d6475f3",
+ "src/opencode/types/shared/config_info.py": "b7b94b242bbf94c0dfb1a167aafaeca8a859dfeb5ed9093b9a740b3011ee2860",
+ "src/opencode/types/shared/config_lsp_server.py": "e4dea40e89e577b8ae5d78ae3795ea9053fbc4ef51fcf5e0dd911bbd6ca61cda",
+ "src/opencode/types/shared/config_model.py": "98569792661a20d700c21aa092da3efa13b5a42d64540b02ee86055e755b22cb",
+ "src/opencode/types/shared/config_model_capabilities.py": "f325d3236aba8c4c7157f724a96773342cb0430550ed5fbdd4d5eb051c3d1b8b",
+ "src/opencode/types/shared/config_model_cost.py": "007949dd04ee90717ee96e6e32273bd6414c122278b713e85074d5e612663074",
+ "src/opencode/types/shared/config_model_reasoning.py": "253c73447731380da9066f5a4f164bc611fa308c9bcb2e500e5bc3d15d51e658",
+ "src/opencode/types/shared/config_model_settings.py": "4fbea4683a1e47bdbb28f7d45bc5b72ed86d51d0627862c30b2cd9ce6ed9dfdb",
+ "src/opencode/types/shared/config_plugin_entry.py": "415e7ab4b612f17145948ff1b5108e879b26eb76fb60641e8103a71dfb913be7",
+ "src/opencode/types/shared/config_provider.py": "aa98b53c812ad19df8cabc73774816322b75e9c913c54fd4cc9ab4b1fe78c6f6",
+ "src/opencode/types/shared/config_provider_settings.py": "19866554e1ba27a9b2afdf8ac50ee478fccbfd5862403aa8848f5b565756eb61",
+ "src/opencode/types/shared/config_reference_git.py": "6a44ba3ad7a9dc23f8777959c219b6167eedd57ecd4e0c60ab4074edc60d93e7",
+ "src/opencode/types/shared/config_reference_local.py": "7f8ab42680c22d25dbbd2857163dfb966e953c54394a91522e4920218cf17351",
+ "src/opencode/types/shared/config_shell_option.py": "f767f54983f5e00260ebfceeab81148c90a95a548e38b2ddec34dd30066b68b2",
+ "src/opencode/types/shared/config_updated_event.py": "e779723028de16a732e3c1dfd79053a17eea6532909b1e05ad8d3844a5a1f4b5",
+ "src/opencode/types/shared/config_warming.py": "d752f98ca377fbe12b9aca9ee80fd7a6b12e71c8982bf77346f3480dcc708698",
+ "src/opencode/types/shared/config_web_search_info.py": "5a75a8b363296b10764403e51637b5c3e0ada99178bf6e9a0e2dea18d14f7b83",
+ "src/opencode/types/shared/config_worktree.py": "699c8775ad3ecfcd0d0b7d2b86021e8481632778b89f7083a629d6b8961b6822",
+ "src/opencode/types/shared/connection_credential_info.py": "26754d64a606b63d6d02c7623ff48aab7ea480cdaea13d81d4c9966947a13c49",
+ "src/opencode/types/shared/connection_env_info.py": "d954af881fa0b64d2af8acc991559d949aab1d5d62e5bb3b2318c47ecc7c7ec5",
+ "src/opencode/types/shared/connection_info.py": "7ab2deb89d5aca201e96d0ec3826cc9ac1b9e76de6adc42772aebc76d0e9160f",
+ "src/opencode/types/shared/connection_status.py": "73af1e5ff5bb54a55da40f4253e9ed340068ac886dd8dad2add0b6cfca4d835f",
+ "src/opencode/types/shared/credential_entry.py": "8d52e0a0407b909778ba8fba1ee89898ac12502a9733b87dd6bdc1476380fccc",
+ "src/opencode/types/shared/credential_external.py": "8bbad31494326b225f73cfcbadb0cacf457de12f191559a1d85c4118ed7dc238",
+ "src/opencode/types/shared/credential_id.py": "a469e92d273dcfb600fdc57b28454950b7d61be9c1bb63dcd1f7953398a2cc39",
+ "src/opencode/types/shared/credential_key.py": "a643b881da912100f32d59228091c7d272115d61739282330a05fb211091d3c0",
+ "src/opencode/types/shared/credential_o_auth.py": "3a104f48264228d990a4b05b829e1725f0a3ca5a273e42b4a7f7989d02fc0708",
+ "src/opencode/types/shared/credential_switched_event.py": "9130af6ffa828e7e89ff2f308c8f59243b224d1102cc2bce6b7b53b0abb94726",
+ "src/opencode/types/shared/credential_updated_event.py": "87db5dfca368abad9aeedfe981d76f46049d308bf9e57e12566f26660fa12872",
+ "src/opencode/types/shared/credential_value.py": "8e73414e040c4bb87a6f7a473a9d3f05bdc4af6a7865f61ff5071dd7799637d9",
+ "src/opencode/types/shared/event.py": "680b57e72c98f412b3c174b2ea00cf6e15d3191e7b55be3d0882686c2527de4f",
+ "src/opencode/types/shared/event_id.py": "bda15ac9664f5b33ab3eb70a60327762e437f2efeedf85838b98433c2074ad62",
+ "src/opencode/types/shared/event_seq.py": "70310f394ba1a751034d6960dcc51982bc1c5fd104990d90c6d54e9942bf13e4",
+ "src/opencode/types/shared/event_version.py": "80c6aa92c898bbb42751f96807832ab877642d6f8eef8e2113c4aaaac924243a",
+ "src/opencode/types/shared/event_version10.py": "c152f8c2c5e78763a5e2121ae886c5a5dcee2ff12816ce2d4b0eff5b3437cd3f",
+ "src/opencode/types/shared/file_diff_info.py": "50db2cbb47e28aa2fafbd16ab5da936408efd7b96c231b65b5737f25addb0fea",
+ "src/opencode/types/shared/file_system_entry.py": "c6c7f8c290b9d63b44c3155551751894122c191651cac7ec575223fbf83ca29b",
+ "src/opencode/types/shared/file_system_write.py": "5b8db6e69cbf37cf00009a2d4a7d2254fe6b3887c11f92fb8d9cda148cb951d7",
+ "src/opencode/types/shared/filesystem_changed_event.py": "bb717eb70c2d4d5dd7a64868e54beb0791496b0356c4ce8b8f9bdadce3427e70",
+ "src/opencode/types/shared/form_answer.py": "6f62de83c30013c21c4ff308b3713b8febc1c19d22d3dcecc54ef2b633250b3e",
+ "src/opencode/types/shared/form_boolean_field.py": "beb7052c73d2acbbbd6aaba82249aeb0c9c4c770bdc1d5059acf926b09d8625d",
+ "src/opencode/types/shared/form_cancelled_event.py": "5c26c88d94282f5df8a9f417a019f2808bec0165a2d73de92dc05e54ab734fd0",
+ "src/opencode/types/shared/form_created_event.py": "3bd9d2cca77c0133255509b7d5697c4ab030cb91508f021a7fef8ac62c9ed458",
+ "src/opencode/types/shared/form_detail.py": "8e3340edf34d513bd52b4738ccf29043c14b822ea64262a7814a111a6652141e",
+ "src/opencode/types/shared/form_external_field.py": "9d11228b88cd8630f5c0490d3dd807ec4ca5b24a6f52bd20a90174ec3480cc22",
+ "src/opencode/types/shared/form_field.py": "e01aef7a1e947bc9caf7f36bd9b650fe5eb3cf82820e56a096ae96252501a123",
+ "src/opencode/types/shared/form_fields.py": "592f5fff27594cc6e54269e5866829b5ac145e258e99dfc64e787ac7a35d757b",
+ "src/opencode/types/shared/form_fields1.py": "87f645021b2f2ac39412cd538000e270c5a6a408a8f80bbf4ed0a1cf6f02f7f3",
+ "src/opencode/types/shared/form_fields2.py": "44c3940b51c829d43cdff9651a7a0d0a7f97097853c3cad106d5743f5a36658a",
+ "src/opencode/types/shared/form_fields3.py": "65afb27565c34be9503df0b88749ec7a59e29ab9aa4ce13a2bf4fd4383e37a85",
+ "src/opencode/types/shared/form_id.py": "589cc8abc15ef9481288390c5dbe19c8010b684f5a3aad5eadb923917d2648ff",
+ "src/opencode/types/shared/form_info.py": "8f1168660e08787bca6175b311ce4658330be44dd81503ddacf25909e200554d",
+ "src/opencode/types/shared/form_integer_field.py": "66983a1c639a200312eea09a1b0ae8731c870262574e629141c68aebdc171a4e",
+ "src/opencode/types/shared/form_metadata.py": "63b8240dc79231cc5026a8c8ad11f0c16d417523c256e2c26bee76e4137a3722",
+ "src/opencode/types/shared/form_multiselect_field.py": "160e19d8596b04f90a1ecca4e0de3c306a4b6b49690be6ad6c551d453acac273",
+ "src/opencode/types/shared/form_number_field.py": "cfc8b1181b00b1d94332f6d36db3769e1d3e368551300ad09579dbca63184dfc",
+ "src/opencode/types/shared/form_option.py": "008d0468315056b7ebe0124726932dc79c32523c2e62ca883c947d4622b96eed",
+ "src/opencode/types/shared/form_replied_event.py": "a86b55058587ea961918d951e101a906ebfb6e37f63e41c9b1c5087a5c84b52f",
+ "src/opencode/types/shared/form_state.py": "b0d972fe00a70fc77d49a741ceefde4faca4852871b1cc0426d19142b3ff94e8",
+ "src/opencode/types/shared/form_string_field.py": "8e158bc686525332065693c33aa0ab8e5e092ed174403ec6e33c3d6a4cc051ba",
+ "src/opencode/types/shared/form_value.py": "065a6b015488d6edcb895516ad75814f47dadc0b463df21d5918ac148276952c",
+ "src/opencode/types/shared/form_when.py": "88b049df4d7def2cfe65a1289cece0ca721358d9e8e93d390f95e46e326c3273",
+ "src/opencode/types/shared/generate_text_response.py": "20ed317c9c617d29379ff1d9a62db03d3d5f9815d707ca39677a600f371b1c35",
+ "src/opencode/types/shared/installation_update_available_event.py": "e60f83bad2b2267f560ff5275e075fe249bb2d688e4648d5deec8f1ab5072bce",
+ "src/opencode/types/shared/installation_updated_event.py": "c8a0e05dbb2e037c65df88a33ea586cb4357502c745f5920de9588fd1f3eb7d7",
+ "src/opencode/types/shared/instruction_entry_info.py": "23ae698497a785059756d432b2c5b52e6346e7e9eeaf52f1037b1733e98f1841",
+ "src/opencode/types/shared/instruction_entry_key.py": "db4a668a4b77a6b9006c2964f618bf991d485cd4cf22d1c75aca544c15e42223",
+ "src/opencode/types/shared/instruction_entry_snapshot.py": "34feb77c781319be5fd6812311944f35369f527d71b63f1d68487eb35d253c62",
+ "src/opencode/types/shared/instruction_hash.py": "cd3410198ba68b14a51551117384768e8bda2d0a1a48561b135ae97411160a71",
+ "src/opencode/types/shared/integration_attempt.py": "bcd0006ebb1411fcfcfe6dc2eeb172f777e81edde1f5d792e4d97e589263dc7a",
+ "src/opencode/types/shared/integration_attempt_id.py": "bbbb5901e9b1860a36848dcab0372d7c31a8e802b82aff945763041babfae06a",
+ "src/opencode/types/shared/integration_attempt_status.py": "2ce52016c58d379c1043dfc36c7dd81ae005006324291e32dbcafb203895686b",
+ "src/opencode/types/shared/integration_command_attempt.py": "31072e4bafead240200ad604af522449402fac2271217de05e3d1af5070f0d5e",
+ "src/opencode/types/shared/integration_command_attempt_status.py": "d8e0d76f867a1e1f8880091b8b6703fe2c49a6a7c53be03fdb166b97d696fe0c",
+ "src/opencode/types/shared/integration_command_method.py": "a70feb1ad080c60d6c4c6fe362abd6e3e27de60998657bfb1284c918b2f8d730",
+ "src/opencode/types/shared/integration_env_method.py": "90183150fac9091e4e6dac61f5aff86cd0a07f2b1f1aa11ab14b29c8ccea2923",
+ "src/opencode/types/shared/integration_external_method.py": "df9a10215c2209e8c43a9cc06e9646b15b05b5344e5a45199df9ecb50072ae50",
+ "src/opencode/types/shared/integration_id.py": "0b1f7ff46da4ed52e1219778c0f86ee5fcbc7248fbb2a7e71e589a43535b1d01",
+ "src/opencode/types/shared/integration_info.py": "08a390bf1e0ca11149327f747a5d05f871df7f050d8c4131d4c95de1cfaf0e43",
+ "src/opencode/types/shared/integration_key_method.py": "9ceb03556db4f03a4e00e13cca7e3d88c21513f6280ea318b772bdea29820629",
+ "src/opencode/types/shared/integration_method.py": "de1c8ad234978aa029255df1f5a6b9f242b209e5984a971b85194841950390fa",
+ "src/opencode/types/shared/integration_method_id.py": "67dfc0d2e13788123f6a44744f386809dddabeaa42e20c739fbc41329234eee5",
+ "src/opencode/types/shared/integration_o_auth_method.py": "a98183bf62385cead6c03a1e08019374774660c5fd779746b4bac63bae20505f",
+ "src/opencode/types/shared/integration_updated_event.py": "8a5a3b3c07ff40c48135f8de4a7e9d69fbb2e0dcfad7cdaa90e29cafd9481cc7",
+ "src/opencode/types/shared/location_public_info.py": "bca814fa0616b1e56900298d62b04f334cac4970e0b8bcf1ff6b1a052c06e292",
+ "src/opencode/types/shared/location_public_ref.py": "036e8549ce64432b5315ddb764bcf1220c78ffcfd0e26b922d7b9e476da55eab",
+ "src/opencode/types/shared/location_ref.py": "9b03b9d69745144e87546df3db0913a03f82cd8872cb64e15e25b7c17668d823",
+ "src/opencode/types/shared/location_shutdown_event.py": "2c23f1e7b8e5d901f2c84613d6e8d2b015b82de0fb4d99774b1acc58b17a96dc",
+ "src/opencode/types/shared/mcp_local_config.py": "cd99435d6f582665cb555518d8a3c48a5eaedfa4302d250bd8a9cf85cc7b06f3",
+ "src/opencode/types/shared/mcp_o_auth_config.py": "c2a5a7380331aaff0660bd140a10f352cd85a2d2ed6a56c1ecc89d6947229e5b",
+ "src/opencode/types/shared/mcp_protocol.py": "6c5d8c23f502120cf9d9acb3670d31e1f761edabde68be46f144d5498aa0a83b",
+ "src/opencode/types/shared/mcp_remote_config.py": "eff9adb2e117b589fe7df16f48443ba20d68bf20b9c9e2073e9625219eb65b45",
+ "src/opencode/types/shared/mcp_resource.py": "7ef69898b94874609718279d8616926b14023aebb87f25c1ae8c98c93b5dbab7",
+ "src/opencode/types/shared/mcp_resource_catalog.py": "579d840bc23588e3e1f463728028f5d603a302db1e3a8cc96ab6c3b562c14fa2",
+ "src/opencode/types/shared/mcp_resource_template.py": "4a79c7a923775c40cb0e3a9ccf2fcaa3cf09f6b0de92fa67ea101e35a2535e8a",
+ "src/opencode/types/shared/mcp_resources_changed_event.py": "a4c970789b1e045a7112b2426e5e587122c3fda1a65a0e59964b99fc660dfe52",
+ "src/opencode/types/shared/mcp_server.py": "c14f8fd28507a442d57439f5c918ecfc3e553e648edf1ce0af783e95321b86ae",
+ "src/opencode/types/shared/mcp_status_changed_event.py": "11461014ac757d90a6274398d87ffde040d8473598bf5aa3b9058cc4804ece93",
+ "src/opencode/types/shared/mcp_status_connected.py": "be49a3c38ad727a84356b005b42f299234f50643b080a30779c5d634310bb141",
+ "src/opencode/types/shared/mcp_status_disabled.py": "e0b54390e0bb8877048451e22e0988923acd2f79f4418f456bf3a479789d8b2e",
+ "src/opencode/types/shared/mcp_status_failed.py": "78dd2124a3bbfaf597e3d378a6098d1c1f37fd1817e9cdb37d96e498e9839dc3",
+ "src/opencode/types/shared/mcp_status_needs_auth.py": "8f2f712bce4cd21de26b3122f7038e718cc288a078df7f3c7010aa28524d96ab",
+ "src/opencode/types/shared/mcp_status_pending.py": "764ead323c314f89798a8a7a9d4b8d8f21159e6c3d75cb2d7e7cef998959a1e7",
+ "src/opencode/types/shared/model_cache_format.py": "2e65ca92697176ca707320c35ff0630c7870282c2539ddf65a71fc2e035ef1b5",
+ "src/opencode/types/shared/model_capabilities.py": "2e7a191152ccb212fc9d59bd96ad80061f0ef7c122e1678a04b0e389e291930d",
+ "src/opencode/types/shared/model_compatibility.py": "4677e14bb85a0f8633ca3cc72237c5f603dfbde4e00e604da1ffeced4ee6ae7a",
+ "src/opencode/types/shared/model_cost.py": "721a97c1e1c77af7a92d3cc645770e6448c7cffd43b6684fc0dc58c56df6c425",
+ "src/opencode/types/shared/model_info.py": "9e615c420fea0c681a2430d1efd5a9f4e221237262f75e09b1c87ca4bd37a44d",
+ "src/opencode/types/shared/model_max_tokens_field.py": "f64a741c3e8f04d7af791b3b51bfe3da4ce47d6c8f0d6c144f4fbf1a9c82e31a",
+ "src/opencode/types/shared/model_reasoning_field.py": "b94b92b729057645071c72e1430b43c0acdcd70899f1894c575e0ee3fdf4661e",
+ "src/opencode/types/shared/model_ref.py": "b1ee18d13746145721ff55564bedefa6e77ddaac09ae13c919a9ccaa59deb121",
+ "src/opencode/types/shared/model_settings.py": "0291db170c49f8342ce7356ec06fbe56d6515d5a9a5a679a3ef0f071fc7ea5ad",
+ "src/opencode/types/shared/model_updated_event.py": "6f6fdbd26196c1685d72fdcfcb109a919d81963123c5b59b053f22b2fd733a91",
+ "src/opencode/types/shared/model_variant.py": "9cb17df07d7f8612bc011fdbd2335cce4f7525719f6437ff536770f182c98ede",
+ "src/opencode/types/shared/models_dev_refreshed_event.py": "6b10e7deb9afb492d3a576791317d84f6804cb08f1c44fb1fcd000851e5e9b25",
+ "src/opencode/types/shared/money_usd.py": "5a246df73be2a46af433ebf6c2a8fb265b11b0efa46506c61cc0d4582fe5c314",
+ "src/opencode/types/shared/money_usd_per_million_tokens.py": "81ba3b60b0a986ca8a4f3a10e38cca2785a15307e4dc2c4c7a9e42dc4f712e8a",
+ "src/opencode/types/shared/pairing_code.py": "a36fb733405ab68171bfe43cbe3e5163df5d4cb81e4add0633c47b181bcabc86",
+ "src/opencode/types/shared/pairing_session.py": "3f7b16bbda95a81de2c40df9ea342b3c8a20263581bc4c992007e3135c880195",
+ "src/opencode/types/shared/permission_asked_event.py": "0816c0ec95866373c4f052b2b272d1809e97b50ff433fec0613625625923de31",
+ "src/opencode/types/shared/permission_effect.py": "9f747933bc013b5e1bcc78ae0cd7d8b472ccad3a09c229a83662e516f0ed4cb0",
+ "src/opencode/types/shared/permission_id.py": "e884461b54a7f2bd8d6a0fe4c7a3c16b05ec0b8fefadaf5872eaccd4a397fc05",
+ "src/opencode/types/shared/permission_replied_event.py": "103b7689f960016949b0f813afbf29911df44aaeaf431203ce5a33af7dd9f288",
+ "src/opencode/types/shared/permission_reply.py": "3ad9f4e421e22298255edd4fcdc42353bd71e13ddf3956fa0e77dc908b35fee8",
+ "src/opencode/types/shared/permission_request.py": "bf468b742718b638f0e43ea1e2c372d475f0671c92dbc06ba92885164c8ebdea",
+ "src/opencode/types/shared/permission_rule.py": "493f8eade7676377e737798abb0da8d712dafd965d2650e7b761e7eefea4dd64",
+ "src/opencode/types/shared/permission_ruleset.py": "65bce69c8957c91443545ac2d8529e5bb50238f48bb78bd9ce55ceb138270525",
+ "src/opencode/types/shared/permission_saved_id.py": "c5f38b422a72e739dda512fa971f78deb3bf138b21decd1ce03db600d2554fb9",
+ "src/opencode/types/shared/permission_saved_info.py": "caec5242e73f8271e28451439587bc2b200e94956e43321c5a583c8129af0a28",
+ "src/opencode/types/shared/permission_source.py": "a1cc1950e6876cb7bef476038125f797393aae845e7fd40f9f909bed13c24c23",
+ "src/opencode/types/shared/persistent_pty_added_event.py": "e39b0bb5bc536ab7776ea8cef36ae9759140482ae31287f63f163f002bb0c185",
+ "src/opencode/types/shared/persistent_pty_handoff.py": "e8bcf9e4de8c12a58ac658563b942ad6d0550de1627b37cd59c614e0d592a49a",
+ "src/opencode/types/shared/persistent_pty_info.py": "5fe39341ff651d995d169caf4b88277c7b25b73c126af19508efd38655b46f81",
+ "src/opencode/types/shared/persistent_pty_read_lines.py": "4587fbc3876abd1420365067cfaef12b7a787a4378c502ccce4a6cdcea5aa719",
+ "src/opencode/types/shared/persistent_pty_read_result.py": "e4e4c0eff7b9e36e5b6968e79b731745e4e58b27ff6ec870cc1964de5f20d612",
+ "src/opencode/types/shared/persistent_pty_removed_event.py": "89c0255d48042950c4fa9ee0b2e726849e200aa6f0f37e54b7fd2e107c0a563d",
+ "src/opencode/types/shared/persistent_pty_snapshot.py": "292cc7004ee7b018a04d8e75801d82114f7e27d21ca83c59a7d461b47f38f27b",
+ "src/opencode/types/shared/plugin_features.py": "e964c193a43877cc4ba73f8f123e717dbd4283b87513c83336a5479faa0c5ef9",
+ "src/opencode/types/shared/plugin_info.py": "d89f6634f4aff16a895f643a84bc10f5c995787a3847e59ea4374900af611c97",
+ "src/opencode/types/shared/plugin_source.py": "7d075c162e9e8f922c6db44b972a90abfa35a494002f334fd3ec25bf8a3ad893",
+ "src/opencode/types/shared/plugin_state.py": "979f94b867fd3ea657f4a078e8b026b4ac04172abb81786ccfb4a0175fc7a231",
+ "src/opencode/types/shared/plugin_updated_event.py": "b098e01800e60be4c14711879788723336e663a8cf0fa21e364f2a8e6774ff40",
+ "src/opencode/types/shared/project.py": "ec80822ce8b517671f8a822d229dc70ffcdd07a8f372812ac2dcb536722f121b",
+ "src/opencode/types/shared/project_commands.py": "dd879fed70424789364b0781d19ab53e466986d699fa050b6cb5bd30de6ab0e7",
+ "src/opencode/types/shared/project_icon.py": "c6c7a39a1054cb096c83afe9acc4ec5e5823fc3d0efe62910d2b4e95b9324098",
+ "src/opencode/types/shared/project_id.py": "65cf3ecafe5ae3f03e5f7781b38a8f7c497cb94db51249cb76221da93e2ce426",
+ "src/opencode/types/shared/project_time.py": "9a8714eba7a54eb6df8640513cd0f30a457a0b99e4fb344675da711daa487279",
+ "src/opencode/types/shared/project_updated_event.py": "9fc59dd40f32481a7d3b285f28f42b4e8c1dcea8a87c5494d7accc1f31cacb1d",
+ "src/opencode/types/shared/project_vcs.py": "b78c28d8302895092613626cfffa8d44e6f1cfcc808f351c1713083b7bff40a1",
+ "src/opencode/types/shared/prompt_agent_attachment.py": "dedc169dc1de69357d8a3bda664ea84ec34dd8ce62c6f1eae33def645b455d42",
+ "src/opencode/types/shared/prompt_base64.py": "1f6d60d09ae65005b673b8e5847ccc996156f06f0bdfa5069d120bfcd0e373bb",
+ "src/opencode/types/shared/prompt_file_attachment.py": "fb4392cdc0b9c0aacf1759fb904aa67cf3056bddec6edc765ff984bbc01797df",
+ "src/opencode/types/shared/prompt_file_source.py": "b8e02d4f3ba0ac7b84576a2d321f19661bfa47b3274f135378fc4929aa1cbefc",
+ "src/opencode/types/shared/prompt_input_file_attachment.py": "f2798df2dbd67f5764e4301a9b8b7d6b1f4786ca75d7845ebdd260e33b9b993f",
+ "src/opencode/types/shared/prompt_input_skill_attachment.py": "1c03bc3a0a3047dde84477ac7ba1eae58419c666c6630daf43686b77bb91ab74",
+ "src/opencode/types/shared/prompt_mention.py": "f2b0555b2f601d52fb541a41a8027b2ed1b0ed8f6aed366f0386eb909203581a",
+ "src/opencode/types/shared/prompt_skill_attachment.py": "82877585ee42f5bc8178999bbf21ae18a4504c8c8fd3eb1b3a1d4ab211f9853f",
+ "src/opencode/types/shared/provider_compaction.py": "0524bccb9604e7cbb5873568f575885ab39a7575bfab5d8d3715e9fa70e78244",
+ "src/opencode/types/shared/provider_compatibility.py": "11eb0af6f288d2a6e6de9bf769385696f91b5a3fdce3ba0e861d47f281d3b15f",
+ "src/opencode/types/shared/provider_encoding.py": "2b95fbabe64d7bedf13eb5bf8d572b4331fc7d8bd388f89244e95a42e4c84fa6",
+ "src/opencode/types/shared/provider_id.py": "5520d27a486a4b53387f58cb905eb1154a032a5f9ef6768af922857889be1e62",
+ "src/opencode/types/shared/provider_info.py": "8a645cb03708c69dba4943400cc1d4214881a751f5393a01fbf505ef0a611715",
+ "src/opencode/types/shared/provider_request.py": "ae7835b5951917695bfac5ff06d87ead9b7e09ab509b9ac1e8c9b911f2d7fe1e",
+ "src/opencode/types/shared/provider_settings.py": "d6241a0dbd3baf26a6f8cdc51a0d1b5e89736d72140ddb82421351482ef947a0",
+ "src/opencode/types/shared/provider_transport.py": "40e877660036d32e8e931f834c0ebf427cab6c1e9e803e4ffc8b5bc3cbce6a5e",
+ "src/opencode/types/shared/provider_updated_event.py": "bcb74a8d532baa5caaa91fda1f15a62720f77f2c9dd4dd673598d42f83c46c95",
+ "src/opencode/types/shared/pty.py": "acc99457f8b62fa5a7aeac5a93be078b771566921d567da54e1c48ab39a77494",
+ "src/opencode/types/shared/pty_created_event.py": "5fc36e03372106630cff8e24e9d1c4d6a2d43f39c1624489e7de6363ed875126",
+ "src/opencode/types/shared/pty_deleted_event.py": "886c0d9ee4ab38d19f1e46c44a15f4f626698d81100f97da7e2806752c71eaca",
+ "src/opencode/types/shared/pty_exited_event.py": "de94797706108e1e838710dc0c6308229742edb572753594114f960f3fa88629",
+ "src/opencode/types/shared/pty_id.py": "2afae68195e23b8a967110f6e56d35bfe98b2612ee553b34676209d034c536f4",
+ "src/opencode/types/shared/pty_ticket_connect_token.py": "c6137912228d34a8876de2ca58e4fa53becbf3f4c1c5ec88c9b2fd5fc5e2773b",
+ "src/opencode/types/shared/pty_updated_event.py": "d0a4e86e0be2c1f3a1900caf3498fdc3f0961e5be9cb20093e47b2cf7c5f9b24",
+ "src/opencode/types/shared/reference_git_source.py": "338c714333b36e5d58894e76fe813ad357bcbfba218f222f7357a68842d5f112",
+ "src/opencode/types/shared/reference_info.py": "bbf030747098cf9855af4efe50ecd5cf64119cef937c5cbd4eae23a670b742e1",
+ "src/opencode/types/shared/reference_local_source.py": "0a22d59d85295279df8f14b7960c4ae3bbe960ceff564b5bead089166cec973c",
+ "src/opencode/types/shared/reference_source.py": "ebe809cccd60447dcf353b44df9bfbc26d70129141551ef3a01954cf8dd27187",
+ "src/opencode/types/shared/reference_updated_event.py": "3978255f2ad41f55d20ac1638972bff50f0cc75366b9fc75a8882658c76b33d2",
+ "src/opencode/types/shared/relative_path.py": "846f723ea1b1c1f3c48843cdda63d34cebd948b7b2153659ae95a304398c7b6b",
+ "src/opencode/types/shared/rpc_event.py": "dc7c0505935098dc56ad46db1beff4bef779313463032fc4eabdb1fa2097b97a",
+ "src/opencode/types/shared/rpc_output.py": "807f7309e84822067c9000ddc31aeaf43e4b4cb69118249eed8c85dc4a204447",
+ "src/opencode/types/shared/server_connected_event.py": "e57cec04121a57ffda4f26654f8030863296711e67ae22a9011b81133377c89e",
+ "src/opencode/types/shared/server_info.py": "6bbf5af109b2a3627c48120c02616402007ceb7c1a664782df2283b2289af1ff",
+ "src/opencode/types/shared/session_agent_selected_event.py": "aab44f5ae67f1d84feba12ed2f4efbe6be5860524401768834150b84847f47f6",
+ "src/opencode/types/shared/session_archived_event.py": "ec53ccb2d1b75829cc7a5e84f6abd9e6d27ad920d52da1f6e2232e560db1d767",
+ "src/opencode/types/shared/session_compaction_delta_event.py": "1d0c5be357911d660cd8677239f1484bf9b787a034c927683919d2876472ae7d",
+ "src/opencode/types/shared/session_compaction_ended_event.py": "8ee1402dea2459c1dfe24fbe632688ef2645aabed228435be97f1239b8536efd",
+ "src/opencode/types/shared/session_compaction_failed_event.py": "b9fa0010301644e5b16334ba9fbfa76f5cc78683de1421646e8802f975a7b40d",
+ "src/opencode/types/shared/session_compaction_started_event.py": "e6083f92a5506244a2785a20ec58cdb55b8e9c0166bdef7ada2a6150752a1a06",
+ "src/opencode/types/shared/session_created_event.py": "6f83d42e666dadc158927d9cf508d16c6b0586ef20fe44821706b97643c87a2e",
+ "src/opencode/types/shared/session_deleted_event.py": "a4cc3f5e452c07910fdf36b51ccd4188789acfae0a49995f4dbc021dc369f777",
+ "src/opencode/types/shared/session_execution_failed_event.py": "14a0d2a201491e95da1ab4959ada8d57f4137c915d3a7046f23c1dbc5a5ed4ad",
+ "src/opencode/types/shared/session_execution_interrupted_event.py": "d682c52c0ff5ce046ef9fc6e4ec46288514aae109e311cbc38e9471dd7b95cb2",
+ "src/opencode/types/shared/session_execution_started_event.py": "bb3ae3c27ed7e79c7dc2295105495e625619188580ac4a5b285f8c9ae58b8736",
+ "src/opencode/types/shared/session_execution_succeeded_event.py": "766518720e2c5fd091b0bd65d75c8eabf55f105a9a089942a68906e5f55bb541",
+ "src/opencode/types/shared/session_fork_boundary.py": "305cf573f37df3b975d051dd5dd3613d93d39e35c153c85ef769287c479385e7",
+ "src/opencode/types/shared/session_forked_event.py": "257f04c11bddad4689fc4019c6cc191bffc4853f1d5dacdc7cf1cdbd67fe5126",
+ "src/opencode/types/shared/session_generate_response.py": "3cba04a5482c45a04032c3552d792e3be0314c3229b3c86966cb3a47936cfc18",
+ "src/opencode/types/shared/session_id.py": "71d5ec00fee3567bb3feb15ae61aa545c2d9c791b9d2fa83de26adb7a70148e8",
+ "src/opencode/types/shared/session_id1.py": "abe58a8620b096ff92564e084842dc3c860e6c570f508cc867c627149c7921fa",
+ "src/opencode/types/shared/session_idle_event.py": "d0315d5011638cbf5d47c0b7d4bb69ed7e5591491c07dcf2f8f788100f6824ce",
+ "src/opencode/types/shared/session_inbox_cancelled_event.py": "617d88fd62f6cd02114844d4247ca01279f71397dd60c7a7f05d8e2b4069574d",
+ "src/opencode/types/shared/session_inbox_compaction.py": "4a761a4e85a6af46ec1a0476889114d4c1ae74fc741dd76aadf2f31d00396037",
+ "src/opencode/types/shared/session_inbox_compaction_payload.py": "084fdbfecc8ebae043e8c3c814ac035e1f609833105aa875f445fe14ffff2a71",
+ "src/opencode/types/shared/session_inbox_delivered_event.py": "2cc752332fd10653bb37a93c6ccd079795782103ee06d80370149f77efdbf90a",
+ "src/opencode/types/shared/session_inbox_delivery.py": "4dae712d09bd0dc3f42769f66153d9d9a8625c9a054ed88fd33259aa3cb5b024",
+ "src/opencode/types/shared/session_inbox_delivery_changed_event.py": "c37b6970ffb7c576ffab20c2da176d8d3aa174a1d57a04686c8622ea4d6d4386",
+ "src/opencode/types/shared/session_inbox_enqueued_event.py": "d6bfed66fb0f7d01eccc9a2eeecb2972eafbf46d8f72791cb0abbbbca4ff5809",
+ "src/opencode/types/shared/session_inbox_info.py": "4392aa9e78150fd4f495801939e9aca3d169af6f5c888300310bbed2fd27d91f",
+ "src/opencode/types/shared/session_inbox_item.py": "4bec11d630b368b78b722a115e3930e20194b8d849c4ea1b97532a6f6f586bfc",
+ "src/opencode/types/shared/session_inbox_move.py": "39ae3da11ebe3fc944d707121f9c574c66b2e5274a24e5dc285eaa96361b0e4a",
+ "src/opencode/types/shared/session_inbox_move_payload.py": "468b391e289ac1f75e39662f6e6c5693c3bbeffc4c9157bb45fa1112523d667d",
+ "src/opencode/types/shared/session_inbox_move_payload1.py": "04fee7d9cb4995c088ae8c8bf2204b1c3baf1cca7dd0c6c33c0b130e51d9aaca",
+ "src/opencode/types/shared/session_inbox_synthetic.py": "47ceeb61e33c5e334f11279bcf88bf4feefb19a30c5db0840e9c8350b2ff78d2",
+ "src/opencode/types/shared/session_inbox_synthetic_payload.py": "e79bf8ad5687fafd79b688e94ac9a8ec80d6d68cb8142bf8224f3df9e9380102",
+ "src/opencode/types/shared/session_inbox_user.py": "e750df0e1a24e1cd7e1cec5e697e4fd3c7ca62aea16cd44d54de7f0941694190",
+ "src/opencode/types/shared/session_inbox_user_payload.py": "63e25a7fd28d6bbc22b427b8db8230ec5b94c15cc7a15c6f7c32d8437c0f8c0b",
+ "src/opencode/types/shared/session_info.py": "fe712f3f80c2409012cf99052bfb07cff4bf7a4040f558586d21eb512678a864",
+ "src/opencode/types/shared/session_instructions_updated_event.py": "bac65b6f6889fab0c4ee017d873017d56c6d1de1e575a14dc5a16cc8b1f4b826",
+ "src/opencode/types/shared/session_interrupt_response.py": "882aa8964264d4472c305bbf174da6ac89c9f6806045d9d07eaa4f5efc80e646",
+ "src/opencode/types/shared/session_message_agent_selected.py": "85a178ef3e15c516a9fa2c1532cdaf3d13334f6a5a45f077a79c8a1b7565ab5f",
+ "src/opencode/types/shared/session_message_assistant.py": "fa524479ec0f3f4c9f9c30e17610492b736e5ca7c9c00d21b818497b878011dc",
+ "src/opencode/types/shared/session_message_assistant_reasoning.py": "8601737a623805c8d3ae04e1365741edf7197a943cad73c7b4be227645d7f419",
+ "src/opencode/types/shared/session_message_assistant_retry.py": "0a367940e23da91e1f3fc9ca22a015b4cd76167400ccb6ff0b9d82a28c76308f",
+ "src/opencode/types/shared/session_message_assistant_text.py": "498c5361084c9423ac3cfaae96fc5af0fb3ef6f3253f34b8f5088e4ef7d29e94",
+ "src/opencode/types/shared/session_message_assistant_tool.py": "8c47e2cbb59ac1ebead8dab9fbd9d93c77540cac29ac5276b9340eba36a11466",
+ "src/opencode/types/shared/session_message_compaction.py": "f9db754d02d03daa7da98a7504f50721a41f615c6a9132d105c80b7d1592ef6b",
+ "src/opencode/types/shared/session_message_compaction_completed.py": "77521c690b1ee23aa630b0f9f6aaf5a997936b55d565d1a3c7deea729db6f5fd",
+ "src/opencode/types/shared/session_message_compaction_failed.py": "d67d2aaf8ef510ee9f23d59fda9b04551ed3912b04a3c309699e93fe1e2562bf",
+ "src/opencode/types/shared/session_message_compaction_running.py": "5e62ce90c4c3c0fe5186aa39f013f90bf5dba59ee270d3e822a09961b9be2dd1",
+ "src/opencode/types/shared/session_message_id.py": "d2fc8b5e17ebcfefa210040f8bb76e178552718f373a8961f1a5f5fe819c6a7a",
+ "src/opencode/types/shared/session_message_idle.py": "8f99383a78bc98f6fbf50a47c95c36d8382522d98ba43bc79ef547b70e5ec77a",
+ "src/opencode/types/shared/session_message_info.py": "40fb27f24140030b02cafee1aa8b3b1db45178e1eb13ad2f1d1a21ebc1b71b9f",
+ "src/opencode/types/shared/session_message_location_switched.py": "40830dc990c800c8ea65349ce4e5a2d216c0833527b73f798286e62a9e814682",
+ "src/opencode/types/shared/session_message_model_selected.py": "bc363efa76f038dcc9081794bf1d523baaa3a0c9d9f40370b83ecb2d0a24b74d",
+ "src/opencode/types/shared/session_message_native.py": "7665041c5707682cf27226ae0d246c28715301c2538736753cd9b7d859ebf483",
+ "src/opencode/types/shared/session_message_native1.py": "6e3809490ea89baf9acd3756db163bb40d88b3679edc23d9a598fb558c47598a",
+ "src/opencode/types/shared/session_message_native10.py": "6739f00809d70dc31e7af49f3cc9b52bbd6db2e9dae6be8934d32cd3cbf86770",
+ "src/opencode/types/shared/session_message_native11.py": "e4df3b3822f52bfc6c5efaa7ec49fac2dea030c72a2e51875fb2f7ce7951cab1",
+ "src/opencode/types/shared/session_message_native12.py": "dd85304dbc5ae2555b9a3b4545c3264352a54a1836b9ed2211e0d02f7de6dd4e",
+ "src/opencode/types/shared/session_message_native2.py": "a2d0171e6f7dce55fbb8be65408c8613adab0ade423fdef0c449ebab68a08af0",
+ "src/opencode/types/shared/session_message_native3.py": "b778a5d835233c369576bb9005130843e82e66335254fdf828a187227f5b378d",
+ "src/opencode/types/shared/session_message_native4.py": "e6e584a8e184f356b99928f3477184aacd7e33d7cedaf503731a8d0af04032b5",
+ "src/opencode/types/shared/session_message_native5.py": "a959423bcd2d6b91568dbf56bf18ac31248afb93b3833d17bfa78cc6b76447a3",
+ "src/opencode/types/shared/session_message_native6.py": "dec1fae66ec07eb6dd796621fed019b39410250612b86259488e24ff363d279c",
+ "src/opencode/types/shared/session_message_native7.py": "e96de911bc759c0ef49a1ee9a12f929d3467e05d7519d01d7104d64530330804",
+ "src/opencode/types/shared/session_message_native8.py": "a5b5f75524107af150bbc63bb9bee2782b63091e1d355728c20188b564bb6996",
+ "src/opencode/types/shared/session_message_native9.py": "e6f08fc9b4e59107a199ceda974312ddcfb9c7d14ec92a004cfa2a49a58a981c",
+ "src/opencode/types/shared/session_message_shell.py": "de5eed492f704cdc33f76f0dbfddbd1faa01aa2c8b0768a77a599cf00113946a",
+ "src/opencode/types/shared/session_message_skill.py": "00bace50b7e0c2f5b2bf8d4a2a0a8cb05a1c805ad2f9fc787fc34891e383122f",
+ "src/opencode/types/shared/session_message_synthetic.py": "ab9f9f30ac338edf7d376887d2eda5ddb53bee441edce17def0a75f025995503",
+ "src/opencode/types/shared/session_message_system.py": "28c41cda050b6a6326c8f5fe3f697f1a75e0df61530bf090154cb0e1536fbe0c",
+ "src/opencode/types/shared/session_message_tool_state_completed.py": "a85011959108a2876b6a9b7511d5f90ac4234de3370b86ea27c7c73b5856b8c4",
+ "src/opencode/types/shared/session_message_tool_state_error.py": "bcb9c9711e174bc2708b512b298ef8ac85b83e41ef8c699c7f017cb2acef4d3a",
+ "src/opencode/types/shared/session_message_tool_state_running.py": "cd472956f1346911fc7bdd0ff8e8bbe24735b39de51050ad8feea7aa2a348cc5",
+ "src/opencode/types/shared/session_message_tool_state_streaming.py": "9ab3ac7df92d9ccabb7c48b1c9cb71b75dfb425bffc4b7e3b35c6f96c1f96aae",
+ "src/opencode/types/shared/session_message_user.py": "25f2fb510b93bde5318dfa556ffff344f10d731452280b16d78a8930e29e7344",
+ "src/opencode/types/shared/session_messages_response.py": "12330744d456ff7364ca4a67f827a18e80d1418b2e16a545062bb9e2294fa6d2",
+ "src/opencode/types/shared/session_metadata.py": "bd748a6703a692d8d5e4a2e657668f93873a366ae2e5d472bcafacef43cd2533",
+ "src/opencode/types/shared/session_metadata_updated_event.py": "648055ad4a53f64af4d1e7a6d50d682c9f7fccc6e849d074473b87134fc18cf1",
+ "src/opencode/types/shared/session_model_selected_event.py": "8962018ca2779acacf742ebae799fb272e8b9d3509f6b690a77cbf23185f95a9",
+ "src/opencode/types/shared/session_moved_event.py": "27c320cd87977d2d327ad68a5fa452fcbf5c5a06c4de2d405b45f6e864db79a6",
+ "src/opencode/types/shared/session_permissions_event.py": "972c8e703c3e0c59f520051154c03cd5fdaeedd781eadc7dc08d38ebfa3584af",
+ "src/opencode/types/shared/session_reasoning_delta_event.py": "778d9060824dd7b3a1f986278e03f1607e9d3ebd5231083987060ec93d838ceb",
+ "src/opencode/types/shared/session_reasoning_ended_event.py": "f348197d74760714150645f96570a265b9c5332695705afea1e2249733094d06",
+ "src/opencode/types/shared/session_reasoning_started_event.py": "59ecc4f61257d339cb81baf88c407f056737d79018d33d7f98c4d536950ad986",
+ "src/opencode/types/shared/session_renamed_event.py": "f5d7adb2d699628d929c9b585a68ad9216da1dda8dfcb4116d2e8cc904e0a0e3",
+ "src/opencode/types/shared/session_retry_scheduled_event.py": "fb2a8e1ba72627f6afa1cb63f2908deaa5431330fa8b3b9e1725dffb3133bc7b",
+ "src/opencode/types/shared/session_revert.py": "32fe86876e855628829094839b5164ab5972d617654b47ea271db63e173c70d5",
+ "src/opencode/types/shared/session_revert_cleared_event.py": "13df319f939fd680db07c1e26734611f42503f5f79ae9b47143faa6d10d0de2c",
+ "src/opencode/types/shared/session_revert_committed_event.py": "ca25a117f41f0e73f1bb33c84188d3cff19c8719464fab93ede9869445fbe23e",
+ "src/opencode/types/shared/session_revert_staged_event.py": "5705783b1f2d525489dd25194b35c437263fe318100e28bbaa8841c8a09c14c3",
+ "src/opencode/types/shared/session_shell_ended_event.py": "2643df7034dac547bed7737e6ef77e7461e01e317e05fbf2ea31bc7d9a68214b",
+ "src/opencode/types/shared/session_shell_started_event.py": "d476a8567ad8ae8ec9b4f4d01d0f735442b8aed1071d868001fe9b2ad2821fed",
+ "src/opencode/types/shared/session_skill_activated_event.py": "427bdee520cea3590336efbd332bc84e0d472f75daa77e5f639c69bfdd8311ea",
+ "src/opencode/types/shared/session_stats_activity.py": "fecd719cc99acd8acfeb7c1fec9d985ce796d982fdd61b7f9bea0ab7ef340cac",
+ "src/opencode/types/shared/session_stats_info.py": "c3970151d8daf216a4e46a7be0e8e2a1e52e57acc8d7b8c120afd39bd3043b11",
+ "src/opencode/types/shared/session_stats_model_usage.py": "de07dd4f3634e862abe00b2960fb2dac08035115c50ca2a81007f506b9263751",
+ "src/opencode/types/shared/session_stats_tool_totals.py": "e391626456448f532b9b390fe3895615b41dff6029b17441175e41fdf2493062",
+ "src/opencode/types/shared/session_stats_tool_usage.py": "d738ebed49d9580ec438404ce239eaae876436cd4e4eed47ea59ec645d5c11d6",
+ "src/opencode/types/shared/session_stats_tools.py": "5f3195e7ea9606184723f2c99f51a7383a981f4c8aeb59d553f6e726832fb076",
+ "src/opencode/types/shared/session_status.py": "4c2962242da81fd2a16eef94d78420c05fc1706075d738c06f914be1508e5aba",
+ "src/opencode/types/shared/session_status_updated.py": "18a7f4d860df196aad8140f21197ef71c1c58479cd6f774c21b113a51ac12a37",
+ "src/opencode/types/shared/session_step_ended_event.py": "a1dcf4b22d16ad83fb04940525680754a5b9cd22f8d24b6ebde5f0b4c2c816c7",
+ "src/opencode/types/shared/session_step_failed_event.py": "f68041c908e0b95c336f0d5c152e36b85347320e8ce52293500b3ca51f4a1acb",
+ "src/opencode/types/shared/session_step_started_event.py": "4f87cdb0709fb046a5fdaddfe4a45339b37c807600496a50aace2a6baab6b765",
+ "src/opencode/types/shared/session_step_streamed_event.py": "d1d26218d6b9f3323be1fe4aaefc974f61c2439f3ef573e729025c239e40a296",
+ "src/opencode/types/shared/session_structured_error.py": "6e2ea48f601ffc39e9bc7b6db17019115b1a6e13bf2567d68eca59b600eea2b7",
+ "src/opencode/types/shared/session_synthetic_event.py": "9f08fc408846c93c4f7732008b4bf5694231371803343182f4dcedb12862e733",
+ "src/opencode/types/shared/session_text_delta_event.py": "feb0c4c32d7940a2fc079041d61e2bb1486d8da31b3c0d626e90ad698bcca51d",
+ "src/opencode/types/shared/session_text_ended_event.py": "b58668b6f4168bde3f24eb864e6d2be95bded9c63db27d0f02b59ef48a55e991",
+ "src/opencode/types/shared/session_text_started_event.py": "1c7fda66cbeba61060684538707cae00bd9195b9d8c790af943297bc808eb407",
+ "src/opencode/types/shared/session_tool_called_event.py": "bb9f73cba012b28162e83947b91e1eb76ea3efbcf3c874431a9410f11f0ee3c9",
+ "src/opencode/types/shared/session_tool_failed_event.py": "d0072529562adde5a993f471a1f14c4855c713fe43200ae7b5f85b0b2e25f7af",
+ "src/opencode/types/shared/session_tool_input_delta_event.py": "4374dec5dc3662f95f394def99896ccabd3d54f2ce053669abe121af1758da9b",
+ "src/opencode/types/shared/session_tool_input_ended_event.py": "b12ed59bab97ae905cefb496d2493fb2e80bf1752cefa7406c92a13d75e61264",
+ "src/opencode/types/shared/session_tool_input_started_event.py": "011971c1e93d9377c9211712664c88a9c1e08b3d06b020cbc893a4f7ff574b68",
+ "src/opencode/types/shared/session_tool_progress_event.py": "c7a29d6a880fdc4e00e287c3c533e7878b51769ba14f8ef2abf46725109ff41a",
+ "src/opencode/types/shared/session_tool_success_event.py": "a6f995bc71c5483059bb2eda8574790016d981bcd1fd6584f4ea56f9371c0892",
+ "src/opencode/types/shared/session_transfer_data.py": "39a96b505b7bcc649199a080d9201a7838b9ea4784682dc649b4892987dc8a15",
+ "src/opencode/types/shared/session_unarchived_event.py": "0c651de22bfb1f975e92cc3b67297acd905effd4ed315156d29b93f085b03009",
+ "src/opencode/types/shared/session_usage_updated_event.py": "a16780ff0a1ab194041c0feb329fc3018da64a8b0ece5660f394da213054b3b1",
+ "src/opencode/types/shared/session_viewed_event.py": "79065920aced07c50d84886e0dc5df60d9274a9e242067121250de3e6e4dbdd9",
+ "src/opencode/types/shared/sessions_cursor.py": "039ac5996ef117c8d5cab9389d7bcd05c7b5e0a2ae616e353613a77983c4968e",
+ "src/opencode/types/shared/sessions_response.py": "7e11a8020c30dbb6ffeb04fe004e4fd99b9a4dd12c18d2541bba40236c2ebd7e",
+ "src/opencode/types/shared/shell_created_event.py": "d7a6a6a64e32a50553dca20d47dc445f9ab8016d93add61f4e6e3c98f8705960",
+ "src/opencode/types/shared/shell_deleted_event.py": "4e4b1b7c7a6d0d81713173a04dc5282f775c8ee20bed0bad6171c98378f324bf",
+ "src/opencode/types/shared/shell_exited_event.py": "1f2002727e9056629e8583c95b46870d1b7a01659470e370825a06b0af7d69c5",
+ "src/opencode/types/shared/shell_id.py": "3b56055a501df86abb8da93270636c48c539827fd07fb0c8f60185ec643a34bc",
+ "src/opencode/types/shared/shell_info.py": "764d429bd705858d86feedf337670d9276887a73f42509167a03688e039f24d5",
+ "src/opencode/types/shared/skill_id.py": "fae093cd60d21fdfd43fc571d32db8011c77fdb1c256568206b70333758b2342",
+ "src/opencode/types/shared/skill_info.py": "a06b1894db144a62e032d551747442b9b0f5e4f26fcdafea452154de4961b097",
+ "src/opencode/types/shared/skill_name.py": "f183f2de5c8c732feeab382356a9061bf1ddbd41586915fa085ea5799b8baa0e",
+ "src/opencode/types/shared/skill_updated_event.py": "0251f111d92ddd6f2d7980a27942d16714ff7963f531257a7f8c70f7d24844a9",
+ "src/opencode/types/shared/snapshot_id.py": "358f883a18f82559b48dbaa31f81ca9f03679bc4bed56728d85fdb1b26e5c58c",
+ "src/opencode/types/shared/token_usage_info.py": "7b98c6f2840a1d84821986a5b9bcae5c476cc28016fe40c72773895c4a589690",
+ "src/opencode/types/shared/tool_content.py": "154803083ad5659c7c8f7d69b825e5b1ce451344dae51a1047b5de1908c55852",
+ "src/opencode/types/shared/tool_file_content.py": "90bf038fbecf506a49046287e4d06912de264e0f73bc1a131e65ea22dd2d9518",
+ "src/opencode/types/shared/tool_text_content.py": "b12910c38d2e817b7b5c6e5edb6ea2b8a2d4d26403ff85bd4cf76e83e9896861",
+ "src/opencode/types/shared/tui_command_execute_event.py": "344f9f97670a1885354a81b69e55ce359edd3a216e794087dcc27c79be0f4a92",
+ "src/opencode/types/shared/tui_prompt_append_event.py": "864ad92cbb7189f4fa6574106822939eefd01330a814cbc56faf98f15fb93627",
+ "src/opencode/types/shared/tui_session_select_event.py": "5888fbd97d2f974f0ddd39ff438aab9be0ee1ef9f0de676575a13cf349fd3653",
+ "src/opencode/types/shared/tui_toast_show_event.py": "01c04b9d4d79dcce3784bbcd590083409c24f13e499158faee1b154db8675c5a",
+ "src/opencode/types/shared/vcs_base.py": "62020b1312f3f91dc297f9c7a9a7b72189a54eecba123882c73617f3d76e86dc",
+ "src/opencode/types/shared/vcs_branch.py": "b6faa8edae73fa4735df8ce4a0b2f140ce3c7da655bb7706d447501c09c20f2d",
+ "src/opencode/types/shared/vcs_branch_list.py": "328615472225b3352957898a9f100ef268d5f0ccb09bf146420a8d977ca31795",
+ "src/opencode/types/shared/vcs_branch_updated_event.py": "36f3536356438dbef074886cceedf20334d99824be789240556f93f2611bc74b",
+ "src/opencode/types/shared/vcs_file_status.py": "23dbe802c507e99a55009b2f53f2563553e136e27386789927f80627bb4baaca",
+ "src/opencode/types/shared/vcs_info.py": "1c3bbe44717c3d36d3eb873908b2c2b6d617195ffff2e452aa51c33c431a08ee",
+ "src/opencode/types/shared/vcs_mode.py": "ef82eafc81a30ca284c7cbdd05223162f9c1e5791f86aa446f9dbc5129012405",
+ "src/opencode/types/shared/voice_session_response.py": "3321ed9b071786f7888e192172a5c5e8a4a0d466b28be722dfe22efedf348c9e",
+ "src/opencode/types/shared/voice_transport.py": "dea19fbc791abfb99dcc6570fa164c08471018ab5390a2038083bda9c3a9f111",
+ "src/opencode/types/shared/voice_transport_input.py": "e9e1e0115a966c16fff41c2b7132530eb45399a93809ab8a9fbf82df1887bf95",
+ "src/opencode/types/shared/voice_web_rtc_transport.py": "44b0d70c422e77043dda147db64f49eb748855df23f5a18a66379c46df43635c",
+ "src/opencode/types/shared/voice_web_rtc_transport_input.py": "4fe2d0a46d9e900005f1f424ebfe31849130423e76ef8e056b0d053819c07461",
+ "src/opencode/types/shared/voice_web_socket_transport.py": "04cc1cd4ba0d725a79d807b8b49a4b77bd4d1d1e6f89b24bb3644efa136a5814",
+ "src/opencode/types/shared/voice_web_socket_transport_input.py": "0c0f618e99ad46c3c3ba21fb309feef9aeb688182f47f9d6fafe0dbf367abf93",
+ "src/opencode/types/shared/web_search_id.py": "83874a07b868c91c398d35f1a1b3070105e662763b7afb785352cfd84e59c9f6",
+ "src/opencode/types/shared/web_search_provider.py": "204deca70e8d4071dc4ca8c4e7829e4b048a3415d3cbdef26dad08b2c38083de",
+ "src/opencode/types/shared/web_search_response.py": "9c0c2f59f837d9dcc48a0d53f18ab71effbc611b4eed818d8aa873d7a6ac0a2b",
+ "src/opencode/types/shared/web_search_result.py": "f674f43692877e635c2b35384c0c4fbddeb07136719bec18ccfb98078f561de3",
+ "src/opencode/types/shared/websearch_updated_event.py": "3269eed2e9699915f8109085424992236a8577f56b0284b294be59f54baec61a",
+ "src/opencode/types/shared/workspace_id.py": "7f44c1838735ca03f47a129c9cdf1e07dd1f92eb4a6a27771c33df2c0aa3f907",
+ "src/opencode/types/shared/worktree_directory.py": "88abb6efc407ff589d33d657ff95663972e2be9171cb1c62854e8437a1143f93",
+ "src/opencode/types/shared/worktree_info.py": "ae2d0ccf44d7b8f33b219e74257a1c18cc7659030d452abab9d92e6fab9630f9",
+ "src/opencode/types/shared/worktree_list.py": "69dacb513f404f186315f02683a0128c3faa7d1de10feb9cfa6ed825074b911e",
+ "src/opencode/types/shared/worktree_resolved_event.py": "0d4ecb3d433f430e18e9af8315326ec847d64bab904ad884d4b50f16c79de638",
+ "src/opencode/types/shared/worktree_updated_event.py": "c26368288113bdff7ec661c2ea77b104b230ef6ab165d56744722c0fad6a7acb",
+ "src/opencode/types/shell_create_params.py": "aa5e7394da958f8f36bcfe14f62f2e9a08890d65cf9e45eb5f7b112598ce83c8",
+ "src/opencode/types/shell_create_response.py": "0b6b2c55f151dc476dc7899194ead80a642dba6f5994d6ee9c80cc994e9b51fe",
+ "src/opencode/types/shell_get_params.py": "ed52da35e0a77297f0def2bf2ee2265776c6456b3bb50dff7f52191e681b972e",
+ "src/opencode/types/shell_get_response.py": "411deb76f6e1ad64eb093809c1db0a7f672b4d10495ea0bf5df0f2aa509d2b35",
+ "src/opencode/types/shell_list_params.py": "6278ddc54818458604af4e0c6f5e1e7a09ba66af35435ef00be758e1c4cc4a5a",
+ "src/opencode/types/shell_list_response.py": "32495bf8ea4edf37d6ba649b9f15823745c908688a532818c0f02f65cc9aa2a0",
+ "src/opencode/types/shell_output_params.py": "510f65714282358d120076189b8eb27b5e24969a543ce5dfa2c76686e4526cde",
+ "src/opencode/types/shell_output_response.py": "06c7ff8dff67280182026b46caf30ff7bd46de4af0253c093acd80b0f369daf1",
+ "src/opencode/types/shell_remove_params.py": "e5a96ad3ec27d532431b94c9d010fdb1b18a7b99f8d1739f3b843bbf8ea8315f",
+ "src/opencode/types/skill_list_params.py": "f7a7b2356c73a6747e9df4b0462edaa03fd26f06026eaa395471048d82e46e56",
+ "src/opencode/types/skill_list_response.py": "3a388a3278c2a583fcf51d435b6d1e980919e33275d75c3725f9f0cf12f3bb00",
+ "src/opencode/types/vc_base_params.py": "d81a12b8d24d95b8c0bffdff21c452a1c9207807b2bafcc955a562b67a649d2c",
+ "src/opencode/types/vc_base_response.py": "30791db11a2371944a92cefd07e927809ef7779b796f012d3425742d4f61c060",
+ "src/opencode/types/vc_diff_params.py": "56aa3aa944feee0b01fa11e5d66359a0492a80a2a7746731a1f2ca231f6c48c2",
+ "src/opencode/types/vc_diff_response.py": "9117d7be750e109d80c7f77ba9503e0aa3eb59f4333ac2c0fa66256ea22a76a3",
+ "src/opencode/types/vc_get_params.py": "835f68f1395ca48a72b811e8b881c3c67fb327dfc82be9aeaf66d66b51fd5ba7",
+ "src/opencode/types/vc_get_response.py": "585f8b3a03211a1cf7078a111e0e6cd6a317def6dbedfe275c64e00080c0d437",
+ "src/opencode/types/vc_init_params.py": "4cb485046fbbc124f763b2c918673a623233b5a2d47dba839dbc33a71833bf0f",
+ "src/opencode/types/vc_status_params.py": "4452880a8f595808f404eab73377ab35ff9ef9933e175da683c708e0dc1196a2",
+ "src/opencode/types/vc_status_response.py": "dbe82a480c56e2099ddebcf898d0251f08a1b7999024dcd1b2eec77227fab4d2",
+ "src/opencode/types/vcs/__init__.py": "6aa742ac46eb50b32e98feb521d12924e5b8629a9500da1bd21eb9751fac654f",
+ "src/opencode/types/vcs/branch_list_params.py": "d4352cd2db7ce0145e99b8d913cf7fbe533ebf6704f96612b4b50d978b441882",
+ "src/opencode/types/vcs/branch_list_response.py": "f19ac95438207f47c0ab9d883778a2aa2c932a83f73508e8f1194d7c41cb44a9",
+ "src/opencode/types/voice_session_params.py": "483e96b275aa30d59f5b019fa6bd5ec6c7b2c363991e31104aba9c05dbbe96ae",
+ "src/opencode/types/websearch_providers_params.py": "cd876e52d75e5e73ee6d707fc01fbd96362eb82ecbff315f0340823a7d3a993e",
+ "src/opencode/types/websearch_providers_response.py": "9e51380d76e876169108cd7a2c66ca730fd1490cf0de7b498185c723f7011ee7",
+ "src/opencode/types/websearch_query_params.py": "b41bf364dd53a78d1af0eb252601ecaf5e35e4d38623b4403e187b6cee00b987",
+ "src/opencode/types/websearch_query_response.py": "19b71107e2fee3f384ae994f761d2211b099b058428388e1c3141951d138b556",
+ "src/opencode/types/worktree_create_params.py": "9767cd22a293c6321f4ae6f6d4761b67e00f81c58d422577b69617bdf33f8c2a",
+ "src/opencode/types/worktree_list_params.py": "e36467cb00ac1d8559285580b39a35faa1c7babdd21ed1ffc10896a88a43eb02",
+ "src/opencode/types/worktree_refresh_params.py": "e01ec8baa91132207c886ef78c7ec46b6b31ae96df12e17838e4b366d2dbdd25",
+ "src/opencode/types/worktree_remove_params.py": "08de3ed791fd36f2458f741e7b38dc9792a21659579207b82389a2a28404b93d",
+ "tests/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
+ "tests/api_resources/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
+ "tests/api_resources/debug/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
+ "tests/api_resources/debug/test_location.py": "8dc2ed87566aac5a4c6412b90be8ab49ad2049c5f18cfd966efb8158750fe73d",
+ "tests/api_resources/experimental/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
+ "tests/api_resources/experimental/test_persistent_pty.py": "d21032caa52a5bc787c795b24b8b57fa6f6ee2d95465d1bc55cc9d8883c3bfea",
+ "tests/api_resources/integration/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
+ "tests/api_resources/integration/test_command.py": "7df2af237dfcb3bf7850a805fbe31004b50b55cbfab17d42c281c740d20e7986",
+ "tests/api_resources/integration/test_connect.py": "d322a261f25500486c483b6da8566584319880939c8866bb36d6452bebf6299c",
+ "tests/api_resources/integration/test_oauth.py": "f62af1022e7a0c73905c6173d4ffd0ec49ca9200f494dc432baee08f05ac2fc1",
+ "tests/api_resources/integration/test_wellknown.py": "6f875993fb7bb0a33acdde7a84b66b06e27d2ba9442b06009932f87de17daa67",
+ "tests/api_resources/mcp/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
+ "tests/api_resources/mcp/test_resource.py": "9da53a0f18921b820d1df920ccc86deac6d961766d6a3bfa513ea0bcfe71b1ff",
+ "tests/api_resources/migration/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
+ "tests/api_resources/migration/test_v1.py": "53cf87accc6a464ab2828563d4f3cc68aab2969b317631d7aec8bbe92a07d21e",
+ "tests/api_resources/permission/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
+ "tests/api_resources/permission/test_request.py": "7777ccc174ac2b7fbf310db96b4bca9340f89e198e110b0a4dcdf1a90f61c180",
+ "tests/api_resources/permission/test_saved.py": "4687f51d8817bbb0c4a0a2f04c635d4acd2efe368dde4b496dc71bdb0fccbcc0",
+ "tests/api_resources/pty/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
+ "tests/api_resources/pty/test_connect.py": "4bd50ef37c3147d86a2415671c794495911fe43b10f690566db3e9b26724d56b",
+ "tests/api_resources/session/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
+ "tests/api_resources/session/instructions/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
+ "tests/api_resources/session/instructions/test_entry.py": "685b557c00560c99590879a310a8d676c9ea3d5e43397784808605241bfa8555",
+ "tests/api_resources/session/test_form.py": "4f7dc1f446d92ed3e9e45b4a758e0e8ee6a475ec9ba516852d879b0c04dc62a9",
+ "tests/api_resources/session/test_inbox.py": "b36d22134a8107b5a5933b80136f00108c278643a433e542ce2cacb686d0821c",
+ "tests/api_resources/session/test_message.py": "08603f6ab41651629caca7b4c6e797708dbc8a2aaf488850afb244c3b8cd07cf",
+ "tests/api_resources/session/test_revert.py": "52dfdae2b0ae6e57051b1e8045bf6d7b6d632e30582f820501e28d918e502522",
+ "tests/api_resources/test_agent.py": "35ab8d142fda8bf9e27baeb229bbb91531da350985436a64c0b50bade38cde10",
+ "tests/api_resources/test_command.py": "8be3e96e7ff2ad6750fc1085ce6d422ca8e1cda3b2443dca2de0953c8f9edee5",
+ "tests/api_resources/test_config.py": "3b646f5206a3143c00ad183ba3a1f7cbc0a588058e8ab9751d316609cd5da218",
+ "tests/api_resources/test_credential.py": "a1165ac001d3a8754562700d12a894858f98cd30360930b6599f4fdb37cbe491",
+ "tests/api_resources/test_event.py": "dea5556b0932db3ed178262dd925e3aeeda69783ebf313de0b6c20dd2f600205",
+ "tests/api_resources/test_file.py": "6b62a1a08c9a76d179e5829e530986df09586373a747986667edda47f4977e33",
+ "tests/api_resources/test_form.py": "35fdd2d079242efe0a6c26c06198910ed89a2cc1421e5f74509dd202fd830ae5",
+ "tests/api_resources/test_generate.py": "339dececa80dc825f8d866cc8f2d03cb206e16adae3c776fead97482bb0d035f",
+ "tests/api_resources/test_integration.py": "d0c6e645d30405dea9bf91f7880d1c1e60d28ede6d25e07de8438cadca501911",
+ "tests/api_resources/test_location.py": "40f6986acf63ed7e0e279c9df41e970405ac2474f95928f28112921d8d16f13d",
+ "tests/api_resources/test_mcp.py": "e99fa31e92a226799a058e9408a9b9bf3ac9290169a8a40019024aae73fe188b",
+ "tests/api_resources/test_message.py": "b7b4654daa42cd2f70d55fdb0f8846d9003eea21327fd6d10122d2cdf4ffe775",
+ "tests/api_resources/test_model.py": "afd0aa11174a17f25cb7a060597d913202359074c20676ee8b0dabed853548bd",
+ "tests/api_resources/test_permission.py": "008c71ba4d2539e2be080ba8330a6c23ef67cc6e0a6c350636bf798276f04b86",
+ "tests/api_resources/test_plugin.py": "2685fa322627066f274e068959165468524db459aa47684976f57e13f35bb05b",
+ "tests/api_resources/test_project.py": "c9765bd34fb9ebaad4ed81bcb96f8375377674ab5a347ac4e662f8a8aa48945f",
+ "tests/api_resources/test_provider.py": "c44d0ea1ce6fc310e919cd2787d673b5af8b1e841f69fa5a3b96534198a58d0d",
+ "tests/api_resources/test_pty.py": "2afd6351a3638aeabb518b97a4d697af0b7f027787ab24d28c5a25d926bd3a6f",
+ "tests/api_resources/test_reference.py": "82661f6cecc6f0fe44d63fd1180817b27869845578f5d71d039916ee1679b8ec",
+ "tests/api_resources/test_rpc.py": "1dabbd8dbdab2902c05721ea5c344bd57f4c4e5c85af15c9ff60dd650baf4907",
+ "tests/api_resources/test_server.py": "bf49f7693308b7ede9fd1cf2e4e555dce88ec560c6e908393020e3ebc7fac9db",
+ "tests/api_resources/test_session.py": "ae59919fef6cb8cab20ce219f7fd58bbef0c34a4ad45aa01ab6404dbedc9f5cb",
+ "tests/api_resources/test_shell.py": "a7351fb690081ad106b0b210a9ae1c8fe0c1a65a2e801487906f0db71b357062",
+ "tests/api_resources/test_skill.py": "cf22c66273943ec809067cfac8d6b9d14f71e93f862b1be615ae2c51933c34cf",
+ "tests/api_resources/test_vcs.py": "353ea1976920276f2360acf7e720b3c4cbec2f081c9161fea09c3765ca71b25f",
+ "tests/api_resources/test_voice.py": "fcef792b9df6c94f3dae3f0d29e1a106a5d0748d1dda7a054caae8040d9f13ca",
+ "tests/api_resources/test_websearch.py": "21e96ecd0b6cce3c1c7b376a54e782241d6ac78b6a6efc9e8dc9ae942ba79823",
+ "tests/api_resources/test_worktree.py": "55ea8bfa15ea226faed5a35fd4a4b8ad0ab87a94249ef09e89a97a5da848400b",
+ "tests/api_resources/vcs/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
+ "tests/api_resources/vcs/test_branch.py": "aaf9fc847c3420217ffb3e61bb88672f8e3572851343b28bd5f78481f68d51ef",
+ "tests/conftest.py": "d78027f663a4bd3645aed8ae0fb54e29eabdd3f823c5b4e19914b23d00b695d7",
+ "tests/sample_file.txt": "d9014c4624844aa5bac314773d6b689ad467fa4e1d1a50a1b8a99d5a95f72ff5",
+ "tests/test_agent_recipes.py": "441160bca6398e19b21d9df20c4252110c6ea956d7e8bed7d423c66875584bed",
+ "tests/test_client.py": "71a84d341fca963f85796323845d37102324befeb26299f99e8b69f0d195bdb3",
+ "tests/test_extract_files.py": "9607c4071ebd885825b797690e8acddd4a8c76b3e2b7a1d8c3e2354b2134665d",
+ "tests/test_files.py": "3ee0304de14f5f9c6da3a8bd45005329072773bcb048b4aa80bb4b58715c3f2d",
+ "tests/test_models.py": "378192fabdd9db34e32b7974ee7f90bcc36644bf7f8f8709cdb37c53b6aa37f9",
+ "tests/test_qs.py": "3d925a7780637c31f1e47314fc90f86ed5fc04c3251bdf2daddb02d283a16e60",
+ "tests/test_required_args.py": "78ae669b63cadb2b2ad13a419af809c0da08d708fa4b00b428a88df5a4a30c43",
+ "tests/test_response.py": "2f96b80ee2571d0e794f5c7a2174aa8428acaf5586f3977dcfdd7891b046b2fb",
+ "tests/test_streaming.py": "195a8bf98b935866fb27882a4ecdb90c6a0f238b6d928d2ecf503075bc27ffe5",
+ "tests/test_transform.py": "c7f2baab9856afc26af2564c685b7fc084720ada4e555130d700def319a3835e",
+ "tests/test_utils/test_datetime_parse.py": "2883730ca3b1cd7b1e2f55b7f1b7a112ce1cbdeeb3802bf4eccaccf53de1e762",
+ "tests/test_utils/test_json.py": "dcc3d43048586a0f66075aa200c5b574ebd250acd59838f43f3e75efd51d46e0",
+ "tests/test_utils/test_path.py": "fb89201a50b9b13f99b6c2c2cbf48de74eef8e2f4b326767914533af5ccb3491",
+ "tests/test_utils/test_proxy.py": "af2e67032828c8316ada6836e089d57163259c1100ef3cba5c23cbf9b0e4789b",
+ "tests/test_utils/test_typing.py": "b58cab7b11368954ea12e8592cb24fac1ff6ebad860f5400f80ccb22623c0414",
+ "tests/utils.py": "e43293d335e2a8744ed14ac300814b7267e0b7c0e6d9a1869bdd3f482007299e"
+ },
+ "forge_commit": "91f52a40c746e8a2ac0271d8be0b7c82cdf28798",
+ "generator": "ferrule-gen 0.0.0",
+ "lock_version": 2,
+ "spec_sha256": "c3399bb9cef8f8fda2757d56ac5cdf093c508479db92ed6423e82c574c17bc90"
+}
diff --git a/llms.txt b/llms.txt
new file mode 100644
index 0000000..fee10a6
--- /dev/null
+++ b/llms.txt
@@ -0,0 +1,9 @@
+# opencode
+
+> Python SDK for the Opencode API, version 0.0.1. Install with `pip install opencode` and import `opencode`.
+
+## Docs
+
+- [README](https://github.com//blob/main/README.md): installation, usage, async client, retries, timeouts
+- [api.md](https://github.com//blob/main/api.md): every resource, method and type
+- [SKILL.md](https://github.com//blob/main/src/opencode/_agent/SKILL.md): agent skill with tested examples
diff --git a/openapi.json b/openapi.json
new file mode 100644
index 0000000..8ebc70c
--- /dev/null
+++ b/openapi.json
@@ -0,0 +1,29772 @@
+{
+ "openapi": "3.1.0",
+ "info": {
+ "title": "OpenCode",
+ "version": "2.1.2",
+ "x-contract-generator": "opencode-python-sdk codegen/extract"
+ },
+ "tags": [
+ {
+ "name": "server"
+ },
+ {
+ "name": "location"
+ },
+ {
+ "name": "agent"
+ },
+ {
+ "name": "plugin"
+ },
+ {
+ "name": "session"
+ },
+ {
+ "name": "message"
+ },
+ {
+ "name": "model"
+ },
+ {
+ "name": "generate"
+ },
+ {
+ "name": "voice"
+ },
+ {
+ "name": "provider"
+ },
+ {
+ "name": "integration"
+ },
+ {
+ "name": "mcp"
+ },
+ {
+ "name": "credential"
+ },
+ {
+ "name": "project"
+ },
+ {
+ "name": "form"
+ },
+ {
+ "name": "permission"
+ },
+ {
+ "name": "file"
+ },
+ {
+ "name": "command"
+ },
+ {
+ "name": "skill"
+ },
+ {
+ "name": "rpc"
+ },
+ {
+ "name": "event"
+ },
+ {
+ "name": "pty"
+ },
+ {
+ "name": "experimental"
+ },
+ {
+ "name": "shell"
+ },
+ {
+ "name": "reference"
+ },
+ {
+ "name": "worktree"
+ },
+ {
+ "name": "vcs"
+ },
+ {
+ "name": "debug"
+ },
+ {
+ "name": "migration"
+ },
+ {
+ "name": "websearch"
+ },
+ {
+ "name": "config"
+ }
+ ],
+ "paths": {
+ "/api/agent": {
+ "get": {
+ "operationId": "agent.list",
+ "tags": [
+ "agent"
+ ],
+ "summary": "List agents",
+ "description": "Retrieve currently registered agents.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List agents",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Agent.Info"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ }
+ },
+ "/api/agent/{agentID}": {
+ "get": {
+ "operationId": "agent.get",
+ "tags": [
+ "agent"
+ ],
+ "summary": "Get agent",
+ "description": "Retrieve a single currently registered agent.",
+ "parameters": [
+ {
+ "name": "agentID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Agent.ID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Get agent",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Agent.Info"
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "AgentNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/AgentNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "get"
+ ]
+ }
+ },
+ "/api/command": {
+ "get": {
+ "operationId": "command.list",
+ "tags": [
+ "command"
+ ],
+ "summary": "List commands",
+ "description": "Retrieve currently registered commands.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List commands",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Command.Info"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ }
+ },
+ "/api/config": {
+ "get": {
+ "operationId": "config.get",
+ "tags": [
+ "config"
+ ],
+ "summary": "Get configuration",
+ "description": "Return configuration documents and discovery sources for the requested location, from lowest to highest priority.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Get configuration",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Config.Entry"
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "get"
+ ]
+ }
+ },
+ "/api/config/shell": {
+ "get": {
+ "operationId": "config.shells",
+ "tags": [
+ "config"
+ ],
+ "summary": "List available shells",
+ "description": "Return shells available to terminal and agent execution.",
+ "responses": {
+ "200": {
+ "description": "List available shells",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/ConfigShell.Option"
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "shells"
+ ]
+ }
+ },
+ "/api/credential": {
+ "get": {
+ "operationId": "credential.list",
+ "tags": [
+ "credential"
+ ],
+ "summary": "List credentials",
+ "description": "List every stored integration credential, including its secret value.",
+ "responses": {
+ "200": {
+ "description": "List credentials",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Credential.Entry"
+ }
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ },
+ "post": {
+ "operationId": "credential.create",
+ "tags": [
+ "credential"
+ ],
+ "summary": "Create credential",
+ "description": "Store an integration credential. It becomes the integration's active credential unless activate is false and the integration already has one. Fails with a conflict when the requested ID already exists.",
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Credential.ID"
+ },
+ "integrationID": {
+ "$ref": "#/components/schemas/Integration.ID"
+ },
+ "label": {
+ "type": "string"
+ },
+ "value": {
+ "$ref": "#/components/schemas/Credential.Value"
+ },
+ "activate": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "integrationID",
+ "value"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Create credential",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/Credential.Entry"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "ConflictError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ConflictErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "create"
+ ]
+ }
+ },
+ "/api/credential/{credentialID}": {
+ "patch": {
+ "operationId": "credential.update",
+ "tags": [
+ "credential"
+ ],
+ "summary": "Update credential",
+ "description": "Update a stored credential label.",
+ "parameters": [
+ {
+ "name": "credentialID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Credential.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "label": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "label"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Update credential"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "update"
+ ]
+ },
+ "delete": {
+ "operationId": "credential.remove",
+ "tags": [
+ "credential"
+ ],
+ "summary": "Remove credential",
+ "description": "Remove a stored integration credential.",
+ "parameters": [
+ {
+ "name": "credentialID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Credential.ID"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Remove credential"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "remove"
+ ]
+ }
+ },
+ "/api/credential/{credentialID}/activate": {
+ "post": {
+ "operationId": "credential.activate",
+ "tags": [
+ "credential"
+ ],
+ "summary": "Activate credential",
+ "description": "Activate a stored integration credential.",
+ "parameters": [
+ {
+ "name": "credentialID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Credential.ID"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Activate credential"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "activate"
+ ]
+ }
+ },
+ "/api/debug/location": {
+ "get": {
+ "operationId": "debug.location.list",
+ "tags": [
+ "debug"
+ ],
+ "summary": "List loaded locations",
+ "description": "List locations currently loaded by the server.",
+ "responses": {
+ "200": {
+ "description": "List loaded locations",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "location",
+ "list"
+ ]
+ },
+ "delete": {
+ "operationId": "debug.location.evict",
+ "tags": [
+ "debug"
+ ],
+ "summary": "Evict a loaded location",
+ "description": "Dispose the requested location's cached services so its next use boots them fresh.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Evict a loaded location"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "location",
+ "evict"
+ ]
+ }
+ },
+ "/api/event": {
+ "get": {
+ "operationId": "event.subscribe",
+ "tags": [
+ "event"
+ ],
+ "summary": "Subscribe to events",
+ "description": "Subscribe to native events and plugin RPC events across all server locations. Volatile by contract: a slow consumer overflows and fails the stream, and events during disconnection are missed.",
+ "responses": {
+ "200": {
+ "description": "Subscribe to events",
+ "content": {
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/V2Event"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "subscribe"
+ ]
+ }
+ },
+ "/api/experimental/config": {
+ "patch": {
+ "operationId": "experimental.config.update",
+ "tags": [
+ "config"
+ ],
+ "summary": "Update global configuration",
+ "description": "Patch supported fields in the highest-precedence global configuration document.",
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "shell": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "shell"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Update global configuration"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "update"
+ ]
+ }
+ },
+ "/api/experimental/fs/write": {
+ "post": {
+ "operationId": "experimental.fs.write",
+ "tags": [
+ "file"
+ ],
+ "summary": "Write file",
+ "description": "Write the raw request body to an absolute path or a path relative to the requested location, creating parent directories, and return the resolved absolute path. Unlike read, the target is not confined to the location. Experimental: may change without compatibility guarantees.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ },
+ {
+ "name": "path",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "description": "An absolute path or a path relative to the requested location. Missing parent directories are created."
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Write file",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "$ref": "#/components/schemas/FileSystem.Write"
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "write"
+ ]
+ }
+ },
+ "/api/experimental/generate": {
+ "post": {
+ "operationId": "experimental.generate.text",
+ "tags": [
+ "generate"
+ ],
+ "summary": "Generate text",
+ "description": "Run one stateless model generation using the server's base configuration and return the assistant text. Uses the base configuration's default model when none is specified.",
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "prompt": {
+ "type": "string"
+ },
+ "model": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Model.Ref"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "prompt"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Generate text",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GenerateTextResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "text"
+ ]
+ }
+ },
+ "/api/experimental/integration/wellknown": {
+ "post": {
+ "operationId": "experimental.integration.wellknown.add",
+ "tags": [
+ "integration"
+ ],
+ "summary": "Add wellknown integration",
+ "description": "Discover and persist an experimental wellknown integration source.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "url"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Add wellknown integration"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "wellknown",
+ "add"
+ ]
+ }
+ },
+ "/api/experimental/mcp/{server}": {
+ "put": {
+ "operationId": "experimental.mcp.add",
+ "tags": [
+ "mcp"
+ ],
+ "summary": "Add MCP server",
+ "description": "Add an MCP server at runtime or replace an existing one, connecting it immediately.",
+ "parameters": [
+ {
+ "name": "server",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "config": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Mcp.LocalConfigEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/Mcp.RemoteConfigEncoded"
+ }
+ ]
+ }
+ },
+ "required": [
+ "config"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Add MCP server"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "add"
+ ]
+ },
+ "delete": {
+ "operationId": "experimental.mcp.remove",
+ "tags": [
+ "mcp"
+ ],
+ "summary": "Remove MCP server",
+ "description": "Stop an MCP server and remove it from the runtime set until restart.",
+ "parameters": [
+ {
+ "name": "server",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Remove MCP server"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "McpServerNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/McpServerNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "remove"
+ ]
+ }
+ },
+ "/api/experimental/mcp/{server}/connect": {
+ "post": {
+ "operationId": "experimental.mcp.connect",
+ "tags": [
+ "mcp"
+ ],
+ "summary": "Connect MCP server",
+ "description": "Connect an MCP server at runtime, overriding a disabled configuration until restart.",
+ "parameters": [
+ {
+ "name": "server",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Connect MCP server"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "McpServerNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/McpServerNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "connect"
+ ]
+ }
+ },
+ "/api/experimental/mcp/{server}/disconnect": {
+ "post": {
+ "operationId": "experimental.mcp.disconnect",
+ "tags": [
+ "mcp"
+ ],
+ "summary": "Disconnect MCP server",
+ "description": "Disconnect an MCP server at runtime, removing its tools until reconnected.",
+ "parameters": [
+ {
+ "name": "server",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Disconnect MCP server"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "McpServerNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/McpServerNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "disconnect"
+ ]
+ }
+ },
+ "/api/experimental/migration/v1": {
+ "get": {
+ "operationId": "experimental.migration.v1.status",
+ "tags": [
+ "migration"
+ ],
+ "summary": "Get V1 migration status",
+ "description": "Return the progress of the V1 to V2 session history migration.",
+ "responses": {
+ "200": {
+ "description": "Get V1 migration status",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "required",
+ "completed"
+ ]
+ }
+ },
+ "required": [
+ "status"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "running"
+ ]
+ },
+ "progress": {
+ "type": "object",
+ "properties": {
+ "label": {
+ "type": "string"
+ },
+ "numerator": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 0
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "denominator": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 0
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "label"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "status",
+ "progress"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "error"
+ ]
+ },
+ "error": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "status",
+ "error"
+ ],
+ "additionalProperties": true
+ }
+ ]
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "v1",
+ "status"
+ ]
+ }
+ },
+ "/api/experimental/persistent-pty/handoff": {
+ "post": {
+ "operationId": "server.experimental.persistentPty.handoff",
+ "tags": [
+ "experimental"
+ ],
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "handoff": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/PersistentPty.Handoff"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "handoff"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "persistentPty",
+ "handoff"
+ ]
+ }
+ },
+ "/api/experimental/persistent-pty/shutdown": {
+ "post": {
+ "operationId": "server.experimental.persistentPty.shutdown",
+ "tags": [
+ "experimental"
+ ],
+ "responses": {
+ "204": {
+ "description": "Success"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "persistentPty",
+ "shutdown"
+ ]
+ }
+ },
+ "/api/experimental/persistent-pty/{ptyID}": {
+ "get": {
+ "operationId": "server.experimental.persistentPty.get",
+ "tags": [
+ "experimental"
+ ],
+ "parameters": [
+ {
+ "name": "ptyID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Pty.ID"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/PersistentPty.Info"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "PtyNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PtyNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "persistentPty",
+ "get"
+ ]
+ },
+ "put": {
+ "operationId": "server.experimental.persistentPty.update",
+ "tags": [
+ "experimental"
+ ],
+ "parameters": [
+ {
+ "name": "ptyID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Pty.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "attachmentID": {
+ "type": "string"
+ },
+ "size": {
+ "type": "object",
+ "properties": {
+ "cols": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "rows": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ }
+ },
+ "required": [
+ "cols",
+ "rows"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "size"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/PersistentPty.Info"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "PtyNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PtyNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "persistentPty",
+ "update"
+ ]
+ },
+ "delete": {
+ "operationId": "server.experimental.persistentPty.remove",
+ "tags": [
+ "experimental"
+ ],
+ "parameters": [
+ {
+ "name": "ptyID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Pty.ID"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Success"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "PtyNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PtyNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "persistentPty",
+ "remove"
+ ]
+ }
+ },
+ "/api/experimental/persistent-pty/{ptyID}/connect-token": {
+ "post": {
+ "operationId": "server.experimental.persistentPty.connectToken",
+ "tags": [
+ "experimental"
+ ],
+ "parameters": [
+ {
+ "name": "ptyID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Pty.ID"
+ }
+ },
+ {
+ "name": "x-opencode-ticket",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/PtyTicket.ConnectToken"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "ForbiddenError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ForbiddenErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "PtyNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PtyNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "persistentPty",
+ "connectToken"
+ ]
+ }
+ },
+ "/api/experimental/persistent-pty/{ptyID}/snapshot": {
+ "get": {
+ "operationId": "server.experimental.persistentPty.snapshot",
+ "tags": [
+ "experimental"
+ ],
+ "parameters": [
+ {
+ "name": "ptyID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Pty.ID"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/PersistentPty.Snapshot"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "PtyNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PtyNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "persistentPty",
+ "snapshot"
+ ]
+ }
+ },
+ "/api/experimental/session/import": {
+ "post": {
+ "operationId": "experimental.session.import",
+ "tags": [
+ "session"
+ ],
+ "summary": "Import session",
+ "description": "Import a projected session transcript at the requested location. Accepts transcripts exported by earlier releases. If parentID is supplied, the parent session must already exist; import parents before children.",
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "info": {
+ "$ref": "#/components/schemas/Session.Info"
+ },
+ "messages": {
+ "type": "array"
+ },
+ "location": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "info",
+ "messages"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Import session",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/Session.Info"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "ConflictError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ConflictErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "import"
+ ]
+ }
+ },
+ "/api/experimental/session/stats": {
+ "get": {
+ "operationId": "experimental.session.stats",
+ "tags": [
+ "session"
+ ],
+ "summary": "Get session statistics",
+ "description": "Aggregate local session activity, usage, and tool reliability for a time range.",
+ "parameters": [
+ {
+ "name": "from",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ {
+ "name": "to",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ {
+ "name": "project",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Project.ID"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ {
+ "name": "timezone",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ {
+ "name": "tools",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "none",
+ "summary",
+ "detail"
+ ]
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Get session statistics",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/SessionStats.Info"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "stats"
+ ]
+ }
+ },
+ "/api/experimental/session/{sessionID}/export": {
+ "get": {
+ "operationId": "experimental.session.export",
+ "tags": [
+ "session"
+ ],
+ "summary": "Export session",
+ "description": "Export a complete projected session transcript.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ {
+ "name": "sanitize",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Export session",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/SessionTransfer.Data"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "UnknownError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnknownErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "export"
+ ]
+ }
+ },
+ "/api/experimental/session/{sessionID}/instructions/entries": {
+ "get": {
+ "operationId": "experimental.session.instructions.entry.list",
+ "tags": [
+ "session"
+ ],
+ "summary": "List instruction entries",
+ "description": "List API-managed instruction entries attached to the session.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List instruction entries",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/InstructionEntry.Info"
+ }
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "instructions",
+ "entry",
+ "list"
+ ]
+ }
+ },
+ "/api/experimental/session/{sessionID}/instructions/entries/{key}": {
+ "put": {
+ "operationId": "experimental.session.instructions.entry.put",
+ "tags": [
+ "session"
+ ],
+ "summary": "Put instruction entry",
+ "description": "Attach or replace one durable instruction entry. Changes announce as updates at the next step boundary.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ {
+ "name": "key",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/InstructionEntry.Key"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "value": {}
+ },
+ "required": [
+ "value"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Put instruction entry"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "413": {
+ "description": "InstructionEntryValueTooLargeError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InstructionEntryValueTooLargeErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "instructions",
+ "entry",
+ "put"
+ ]
+ },
+ "delete": {
+ "operationId": "experimental.session.instructions.entry.remove",
+ "tags": [
+ "session"
+ ],
+ "summary": "Remove instruction entry",
+ "description": "Remove one instruction entry; the removal is announced to the model at the next step boundary.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ {
+ "name": "key",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/InstructionEntry.Key"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Remove instruction entry"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "instructions",
+ "entry",
+ "remove"
+ ]
+ }
+ },
+ "/api/experimental/session/{sessionID}/log": {
+ "get": {
+ "operationId": "session.log",
+ "tags": [
+ "session"
+ ],
+ "summary": "Read the session log",
+ "description": "Experimental durable session event log. Reads events after an exclusive aggregate sequence and continues with live events when follow=true.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ {
+ "name": "after",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ {
+ "name": "follow",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Read the session log",
+ "content": {
+ "text/event-stream": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionLogItem"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "log"
+ ]
+ }
+ },
+ "/api/experimental/session/{sessionID}/skill": {
+ "post": {
+ "operationId": "experimental.session.skill",
+ "tags": [
+ "session"
+ ],
+ "summary": "Activate skill",
+ "description": "Activate a skill for a session by appending a skill message and resuming execution.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Skill.ID"
+ },
+ "resume": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "id"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Activate skill"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, SkillNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/SkillNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "skill"
+ ]
+ }
+ },
+ "/api/experimental/session/{sessionID}/terminal": {
+ "get": {
+ "operationId": "server.experimental.persistentPty.list",
+ "tags": [
+ "experimental"
+ ],
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PersistentPty.Info"
+ }
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "persistentPty",
+ "list"
+ ]
+ },
+ "post": {
+ "operationId": "server.experimental.persistentPty.create",
+ "tags": [
+ "experimental"
+ ],
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "command": {
+ "type": "string"
+ },
+ "args": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "cwd": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "env": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "size": {
+ "type": "object",
+ "properties": {
+ "cols": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "rows": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ }
+ },
+ "required": [
+ "cols",
+ "rows"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "args",
+ "title",
+ "env"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/PersistentPty.Info"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "persistentPty",
+ "create"
+ ]
+ }
+ },
+ "/api/experimental/session/{sessionID}/terminal/read": {
+ "get": {
+ "operationId": "server.experimental.persistentPty.read",
+ "tags": [
+ "experimental"
+ ],
+ "summary": "Read the session's most recently controlled terminal",
+ "description": "Read the last physical rows without changing selection or taking control. Omitted lines uses the live terminal height; larger counts include retained history. Blank rows are preserved. Screen dimensions and cursor remain relative to the live screen. Returns null when no current terminal exists. Selection is server-local and resets on restart. Experimental: may change without compatibility guarantees.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ {
+ "name": "lines",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/PersistentPty.ReadLines"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Read the session's most recently controlled terminal",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/PersistentPty.ReadResult"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "persistentPty",
+ "read"
+ ]
+ }
+ },
+ "/api/experimental/session/{sessionID}/wait": {
+ "post": {
+ "operationId": "experimental.session.wait",
+ "tags": [
+ "session"
+ ],
+ "summary": "Wait for session",
+ "description": "Wait for a session agent loop to become idle.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Wait for session"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "wait"
+ ]
+ }
+ },
+ "/api/experimental/voice/session": {
+ "post": {
+ "operationId": "experimental.voice.session",
+ "tags": [
+ "voice"
+ ],
+ "summary": "Create voice session",
+ "description": "Create a server-brokered voice session using the server's configured provider credentials and return the WebRTC SDP answer or WebSocket ephemeral token.",
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "model": {
+ "type": "string"
+ },
+ "session": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "transport": {
+ "$ref": "#/components/schemas/VoiceTransportInput"
+ }
+ },
+ "required": [
+ "model",
+ "transport"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Create voice session",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VoiceSessionResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "session"
+ ]
+ }
+ },
+ "/api/form": {
+ "get": {
+ "operationId": "form.list",
+ "tags": [
+ "form"
+ ],
+ "summary": "List pending forms",
+ "description": "Retrieve pending forms for a location.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List pending forms",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Form.Info"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ }
+ },
+ "/api/fs/find": {
+ "get": {
+ "operationId": "fs.find",
+ "tags": [
+ "file"
+ ],
+ "summary": "Find files",
+ "description": "Find recursively ranked filesystem entries relative to the requested location.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ },
+ {
+ "name": "query",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "type",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "file",
+ "directory"
+ ]
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Find files",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FileSystem.Entry"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "find"
+ ]
+ }
+ },
+ "/api/fs/list": {
+ "get": {
+ "operationId": "fs.list",
+ "tags": [
+ "file"
+ ],
+ "summary": "List directory",
+ "description": "List direct children using an absolute path or a path relative to the requested location, including parents and siblings outside its directory. Entry paths remain relative to the requested location; listing does not switch locations.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ },
+ {
+ "name": "path",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "An absolute path or a path relative to the requested location. Defaults to the location directory."
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List directory",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FileSystem.Entry"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "FileNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/FileNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ }
+ },
+ "/api/fs/read/{path}": {
+ "get": {
+ "operationId": "fs.read",
+ "tags": [
+ "file"
+ ],
+ "summary": "Read file",
+ "description": "Stream one file relative to the requested location with ETag, Last-Modified, and single-range HTTP Range support (206 Partial Content, 416 Range Not Satisfiable, and 304 Not Modified; multi-range and malformed Range headers fall back to 200).",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ },
+ {
+ "name": "path",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Read file",
+ "content": {
+ "application/octet-stream": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "FileNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/FileNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "read"
+ ]
+ }
+ },
+ "/api/info": {
+ "get": {
+ "operationId": "server.info",
+ "tags": [
+ "server"
+ ],
+ "summary": "Get server info",
+ "description": "Return the server identity, connection URLs, paths, and readiness status.",
+ "responses": {
+ "200": {
+ "description": "Get server info",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServerInfo"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "info"
+ ]
+ }
+ },
+ "/api/integration": {
+ "get": {
+ "operationId": "integration.list",
+ "tags": [
+ "integration"
+ ],
+ "summary": "List integrations",
+ "description": "Retrieve available integrations and their authentication methods.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List integrations",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Integration.Info"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ }
+ },
+ "/api/integration/{integrationID}": {
+ "get": {
+ "operationId": "integration.get",
+ "tags": [
+ "integration"
+ ],
+ "summary": "Get integration",
+ "description": "Retrieve one integration and its authentication methods.",
+ "parameters": [
+ {
+ "name": "integrationID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Integration.ID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Get integration",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Integration.Info"
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "IntegrationNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/IntegrationNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "get"
+ ]
+ }
+ },
+ "/api/integration/{integrationID}/connect/command": {
+ "post": {
+ "operationId": "integration.command.connect",
+ "tags": [
+ "integration"
+ ],
+ "summary": "Begin command connection",
+ "description": "Start a command authentication attempt.",
+ "parameters": [
+ {
+ "name": "integrationID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Integration.ID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "methodID": {
+ "$ref": "#/components/schemas/Integration.MethodID"
+ },
+ "label": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "methodID"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Begin command connection",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Integration.CommandAttempt"
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "IntegrationNotFoundError, IntegrationMethodNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/IntegrationNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/IntegrationMethodNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "command",
+ "connect"
+ ]
+ }
+ },
+ "/api/integration/{integrationID}/connect/command/{attemptID}": {
+ "get": {
+ "operationId": "integration.command.status",
+ "tags": [
+ "integration"
+ ],
+ "summary": "Get command attempt status",
+ "description": "Poll the current status and output of a command authentication attempt.",
+ "parameters": [
+ {
+ "name": "integrationID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Integration.ID"
+ }
+ },
+ {
+ "name": "attemptID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Integration.AttemptID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Get command attempt status",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Integration.CommandAttemptStatus"
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "IntegrationNotFoundError, IntegrationAttemptNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/IntegrationNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/IntegrationAttemptNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "command",
+ "status"
+ ]
+ },
+ "delete": {
+ "operationId": "integration.command.cancel",
+ "tags": [
+ "integration"
+ ],
+ "summary": "Cancel command connection",
+ "description": "Cancel a command authentication attempt and terminate its process.",
+ "parameters": [
+ {
+ "name": "integrationID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Integration.ID"
+ }
+ },
+ {
+ "name": "attemptID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Integration.AttemptID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Cancel command connection"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "command",
+ "cancel"
+ ]
+ }
+ },
+ "/api/integration/{integrationID}/connect/external": {
+ "post": {
+ "operationId": "integration.connect.external",
+ "tags": [
+ "integration"
+ ],
+ "summary": "Connect with external credentials",
+ "description": "Run an external authentication method and store a reference to its credential source.",
+ "parameters": [
+ {
+ "name": "integrationID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Integration.ID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "methodID": {
+ "$ref": "#/components/schemas/Integration.MethodID"
+ },
+ "answer": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Form.Answer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "label": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "methodID"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Connect with external credentials"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "IntegrationNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/IntegrationNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "connect",
+ "external"
+ ]
+ }
+ },
+ "/api/integration/{integrationID}/connect/key": {
+ "post": {
+ "operationId": "integration.connect.key",
+ "tags": [
+ "integration"
+ ],
+ "summary": "Connect with key",
+ "description": "Run a key authentication method and store the resulting credential.",
+ "parameters": [
+ {
+ "name": "integrationID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Integration.ID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "key": {
+ "type": "string"
+ },
+ "answer": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Form.Answer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "label": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "key"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Connect with key"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "IntegrationNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/IntegrationNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "connect",
+ "key"
+ ]
+ }
+ },
+ "/api/integration/{integrationID}/connect/oauth": {
+ "post": {
+ "operationId": "integration.oauth.connect",
+ "tags": [
+ "integration"
+ ],
+ "summary": "Begin OAuth connection",
+ "description": "Start an OAuth attempt and return the authorization details.",
+ "parameters": [
+ {
+ "name": "integrationID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Integration.ID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "methodID": {
+ "$ref": "#/components/schemas/Integration.MethodID"
+ },
+ "answer": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Form.Answer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "label": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "methodID"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Begin OAuth connection",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Integration.AttemptEncoded"
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "oauth",
+ "connect"
+ ]
+ }
+ },
+ "/api/integration/{integrationID}/connect/oauth/{attemptID}": {
+ "get": {
+ "operationId": "integration.oauth.status",
+ "tags": [
+ "integration"
+ ],
+ "summary": "Get OAuth attempt status",
+ "description": "Poll the current status of an OAuth attempt.",
+ "parameters": [
+ {
+ "name": "integrationID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Integration.ID"
+ }
+ },
+ {
+ "name": "attemptID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Integration.AttemptID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Get OAuth attempt status",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Integration.AttemptStatus"
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "IntegrationNotFoundError, IntegrationAttemptNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/IntegrationNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/IntegrationAttemptNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "oauth",
+ "status"
+ ]
+ },
+ "delete": {
+ "operationId": "integration.oauth.cancel",
+ "tags": [
+ "integration"
+ ],
+ "summary": "Cancel OAuth connection",
+ "description": "Cancel an OAuth attempt and release its resources.",
+ "parameters": [
+ {
+ "name": "integrationID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Integration.ID"
+ }
+ },
+ {
+ "name": "attemptID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Integration.AttemptID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Cancel OAuth connection"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "oauth",
+ "cancel"
+ ]
+ }
+ },
+ "/api/integration/{integrationID}/connect/oauth/{attemptID}/complete": {
+ "post": {
+ "operationId": "integration.oauth.complete",
+ "tags": [
+ "integration"
+ ],
+ "summary": "Complete OAuth connection",
+ "description": "Complete a code-based OAuth attempt and store the resulting credential.",
+ "parameters": [
+ {
+ "name": "integrationID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Integration.ID"
+ }
+ },
+ {
+ "name": "attemptID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Integration.AttemptID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Complete OAuth connection"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "IntegrationNotFoundError, IntegrationAttemptNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/IntegrationNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/IntegrationAttemptNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "oauth",
+ "complete"
+ ]
+ }
+ },
+ "/api/location": {
+ "get": {
+ "operationId": "location.get",
+ "tags": [
+ "location"
+ ],
+ "summary": "Get location",
+ "description": "Resolve the requested location or the server default location.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Get location",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Location.PublicInfo"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "get"
+ ]
+ }
+ },
+ "/api/location/reload": {
+ "post": {
+ "operationId": "location.reload",
+ "tags": [
+ "location"
+ ],
+ "summary": "Reload configuration",
+ "description": "Shut down and rebuild every loaded location. Pending permissions and forms are cancelled; running sessions continue with fresh services at the next step boundary. Emits location.shutdown for client recovery and responds once all replacement builds settle.",
+ "responses": {
+ "204": {
+ "description": "Reload configuration"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "reload"
+ ]
+ }
+ },
+ "/api/mcp": {
+ "get": {
+ "operationId": "mcp.list",
+ "tags": [
+ "mcp"
+ ],
+ "summary": "List MCP servers",
+ "description": "Retrieve configured MCP servers and their connection status.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List MCP servers",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Mcp.Server"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ }
+ },
+ "/api/mcp/resource": {
+ "get": {
+ "operationId": "mcp.resource.catalog",
+ "tags": [
+ "mcp"
+ ],
+ "summary": "List MCP resources",
+ "description": "Retrieve resources and resource templates from connected MCP servers.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List MCP resources",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Mcp.ResourceCatalog"
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "resource",
+ "catalog"
+ ]
+ }
+ },
+ "/api/model": {
+ "get": {
+ "operationId": "model.list",
+ "tags": [
+ "model"
+ ],
+ "summary": "List models",
+ "description": "Retrieve the current snapshot of available models ordered by release date. The snapshot may precede initial plugin settlement.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List models",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Model.Info"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ }
+ },
+ "/api/model/default": {
+ "get": {
+ "operationId": "model.default",
+ "tags": [
+ "model"
+ ],
+ "summary": "Get default model",
+ "description": "Retrieve the model used when a session has no explicit model selection.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Get default model",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Model.Info"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "default"
+ ]
+ }
+ },
+ "/api/pair": {
+ "post": {
+ "operationId": "server.pair",
+ "tags": [
+ "server"
+ ],
+ "summary": "Create pairing code",
+ "description": "Create a short-lived, single-use code for a /auth/connect/:code pairing link.",
+ "responses": {
+ "200": {
+ "description": "Create pairing code",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PairingCode"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "pair"
+ ]
+ }
+ },
+ "/api/permission/request": {
+ "get": {
+ "operationId": "permission.request.list",
+ "tags": [
+ "permission"
+ ],
+ "summary": "List pending permission requests",
+ "description": "Retrieve pending permission requests for a location.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List pending permission requests",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Permission.Request"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "request",
+ "list"
+ ]
+ }
+ },
+ "/api/permission/saved": {
+ "get": {
+ "operationId": "permission.saved.list",
+ "tags": [
+ "permission"
+ ],
+ "summary": "List saved permissions",
+ "description": "Retrieve saved permissions, optionally filtered by project.",
+ "parameters": [
+ {
+ "name": "projectID",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Project.ID"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List saved permissions",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PermissionSaved.Info"
+ }
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "saved",
+ "list"
+ ]
+ }
+ },
+ "/api/permission/saved/{id}": {
+ "patch": {
+ "operationId": "permission.saved.update",
+ "tags": [
+ "permission"
+ ],
+ "summary": "Update saved permission",
+ "description": "Change the action or resource of a saved permission. Fails with a conflict when the project already has a saved permission with the same action and resource.",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/PermissionSaved.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "action": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "resource": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Update saved permission",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/PermissionSaved.Info"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "PermissionSavedNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/PermissionSavedNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "ConflictError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ConflictErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "saved",
+ "update"
+ ]
+ },
+ "delete": {
+ "operationId": "permission.saved.remove",
+ "tags": [
+ "permission"
+ ],
+ "summary": "Remove saved permission",
+ "description": "Remove a saved permission by ID.",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/PermissionSaved.ID"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Remove saved permission"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "saved",
+ "remove"
+ ]
+ }
+ },
+ "/api/plugin": {
+ "get": {
+ "operationId": "plugin.list",
+ "tags": [
+ "plugin"
+ ],
+ "summary": "List plugins",
+ "description": "Retrieve enabled server plugins and their current status.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List plugins",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Plugin.Info"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ }
+ },
+ "/api/plugin/check": {
+ "post": {
+ "operationId": "plugin.check",
+ "tags": [
+ "plugin"
+ ],
+ "summary": "Check plugin updates",
+ "description": "Check one or all package plugins for available updates.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "target": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Check plugin updates",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Plugin.Info"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "check"
+ ]
+ }
+ },
+ "/api/plugin/update": {
+ "post": {
+ "operationId": "plugin.update",
+ "tags": [
+ "plugin"
+ ],
+ "summary": "Update plugins",
+ "description": "Update package plugins concurrently and notify active locations to reload them. Responds once every update has finished; fails when any update fails.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "targets": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "targets"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Update plugins"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "update"
+ ]
+ }
+ },
+ "/api/project": {
+ "get": {
+ "operationId": "project.list",
+ "tags": [
+ "project"
+ ],
+ "summary": "List projects",
+ "description": "List known projects. Archived projects are excluded unless archived=true.",
+ "parameters": [
+ {
+ "name": "archived",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List projects",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Project"
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ }
+ },
+ "/api/project/{projectID}": {
+ "patch": {
+ "operationId": "project.update",
+ "tags": [
+ "project"
+ ],
+ "summary": "Update project",
+ "description": "Update the project canonical directory, display metadata, and workspace commands.",
+ "parameters": [
+ {
+ "name": "projectID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Project.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "canonical": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ },
+ "name": {
+ "type": "string"
+ },
+ "icon": {
+ "$ref": "#/components/schemas/Project.Icon"
+ },
+ "commands": {
+ "$ref": "#/components/schemas/Project.Commands"
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Update project",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Project"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "ProjectNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/ProjectNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "update"
+ ]
+ }
+ },
+ "/api/provider": {
+ "get": {
+ "operationId": "provider.list",
+ "tags": [
+ "provider"
+ ],
+ "summary": "List providers",
+ "description": "Retrieve active AI providers so clients can show provider availability and configuration.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List providers",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Provider.Info"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ }
+ },
+ "/api/provider/{providerID}": {
+ "get": {
+ "operationId": "provider.get",
+ "tags": [
+ "provider"
+ ],
+ "summary": "Get provider",
+ "description": "Retrieve a single AI provider so clients can inspect its availability and endpoint settings.",
+ "parameters": [
+ {
+ "name": "providerID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Provider.ID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Get provider",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Provider.Info"
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "ProviderNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/ProviderNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "get"
+ ]
+ }
+ },
+ "/api/pty": {
+ "get": {
+ "operationId": "pty.list",
+ "tags": [
+ "pty"
+ ],
+ "summary": "List PTY sessions",
+ "description": "List PTY sessions for a location, including exited sessions retained until removal.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List PTY sessions",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Pty"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ },
+ "post": {
+ "operationId": "pty.create",
+ "tags": [
+ "pty"
+ ],
+ "summary": "Create PTY session",
+ "description": "Create a pseudo-terminal session for a location.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "command": {
+ "type": "string"
+ },
+ "args": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "cwd": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "env": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Create PTY session",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Pty"
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "create"
+ ]
+ }
+ },
+ "/api/pty/{ptyID}": {
+ "get": {
+ "operationId": "pty.get",
+ "tags": [
+ "pty"
+ ],
+ "summary": "Get PTY session",
+ "description": "Get one PTY session, including its exit code once exited.",
+ "parameters": [
+ {
+ "name": "ptyID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Pty.ID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Get PTY session",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Pty"
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "PtyNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/PtyNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "get"
+ ]
+ },
+ "put": {
+ "operationId": "pty.update",
+ "tags": [
+ "pty"
+ ],
+ "summary": "Update PTY session",
+ "description": "Update the title or viewport size of one PTY session.",
+ "parameters": [
+ {
+ "name": "ptyID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Pty.ID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "title": {
+ "type": "string"
+ },
+ "size": {
+ "type": "object",
+ "properties": {
+ "rows": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "cols": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ }
+ },
+ "required": [
+ "rows",
+ "cols"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Update PTY session",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Pty"
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "PtyNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/PtyNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "update"
+ ]
+ },
+ "delete": {
+ "operationId": "pty.remove",
+ "tags": [
+ "pty"
+ ],
+ "summary": "Remove PTY session",
+ "description": "Terminate and remove one PTY session.",
+ "parameters": [
+ {
+ "name": "ptyID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Pty.ID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Remove PTY session"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "PtyNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/PtyNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "remove"
+ ]
+ }
+ },
+ "/api/pty/{ptyID}/connect-token": {
+ "post": {
+ "operationId": "pty.connect.token",
+ "tags": [
+ "pty"
+ ],
+ "summary": "Create PTY WebSocket token",
+ "description": "Create a short-lived single-use ticket for opening a PTY WebSocket connection.",
+ "parameters": [
+ {
+ "name": "ptyID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Pty.ID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ },
+ {
+ "name": "x-opencode-ticket",
+ "in": "header",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Create PTY WebSocket token",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "$ref": "#/components/schemas/PtyTicket.ConnectToken"
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "ForbiddenError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ForbiddenErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "PtyNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/PtyNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "connect",
+ "token"
+ ]
+ }
+ },
+ "/api/reference": {
+ "get": {
+ "operationId": "reference.list",
+ "tags": [
+ "reference"
+ ],
+ "summary": "List references",
+ "description": "List references available in the requested location.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List references",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Reference.Info"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ }
+ },
+ "/api/rpc/{rpcID}/{method}": {
+ "post": {
+ "operationId": "rpc.call",
+ "tags": [
+ "rpc"
+ ],
+ "summary": "Call a plugin RPC",
+ "description": "Dispatch a method to the currently registered RPC at the requested location.",
+ "parameters": [
+ {
+ "name": "rpcID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "method",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "input": {}
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Call a plugin RPC",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Rpc.Output"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "RpcError, InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/RpcErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "RpcInternalError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RpcInternalErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "call"
+ ]
+ }
+ },
+ "/api/session": {
+ "get": {
+ "operationId": "session.list",
+ "tags": [
+ "session"
+ ],
+ "summary": "List sessions",
+ "description": "Retrieve sessions in the requested order. Items keep that order across pages; use cursor.next or cursor.previous to move through the ordered list.",
+ "parameters": [
+ {
+ "name": "limit",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Maximum number of sessions to return. Defaults to the newest 50 sessions."
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ]
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Session order for the first page. Use desc for newest first or asc for oldest first."
+ }
+ },
+ {
+ "name": "search",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ {
+ "name": "parentID",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Filter by parent session. Use null to return only root sessions."
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ {
+ "name": "archived",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "boolean",
+ "description": "Filter by archive state. Use true to return only archived sessions or false to exclude them. Omit to include both."
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ {
+ "name": "directory",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/AbsolutePath"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ {
+ "name": "project",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Project.ID"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ {
+ "name": "subpath",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/RelativePath"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ {
+ "name": "cursor",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionsCursor"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List sessions",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionsResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidCursorError, InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/InvalidCursorErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ },
+ "post": {
+ "operationId": "session.create",
+ "tags": [
+ "session"
+ ],
+ "summary": "Create session",
+ "description": "Create a session at the requested location. A parentID creates a linked child session at its parent's location.",
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "parentID": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "title": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "agent": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Agent.ID"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "model": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Model.Ref"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "location": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "metadata": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Metadata"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "permissions": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Permission.Ruleset"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Create session",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/Session.Info"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "create"
+ ]
+ }
+ },
+ "/api/session/active": {
+ "get": {
+ "operationId": "session.active",
+ "tags": [
+ "session"
+ ],
+ "summary": "List active sessions",
+ "description": "Retrieve foreground Session drains currently owned by this OpenCode process. Sessions absent from the result are inactive.",
+ "responses": {
+ "200": {
+ "description": "List active sessions",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "patternProperties": {
+ "^ses": {
+ "$ref": "#/components/schemas/SessionActive"
+ }
+ },
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "active"
+ ]
+ }
+ },
+ "/api/session/{sessionID}": {
+ "get": {
+ "operationId": "session.get",
+ "tags": [
+ "session"
+ ],
+ "summary": "Get session",
+ "description": "Retrieve a session by ID.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Get session",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/Session.Info"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "get"
+ ]
+ },
+ "delete": {
+ "operationId": "session.remove",
+ "tags": [
+ "session"
+ ],
+ "summary": "Delete session",
+ "description": "Delete a session and its child sessions.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Delete session"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "remove"
+ ]
+ },
+ "patch": {
+ "operationId": "session.update",
+ "tags": [
+ "session"
+ ],
+ "summary": "Update session",
+ "description": "Update mutable session properties.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "title": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "metadata": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Metadata"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "permissions": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Permission.Ruleset"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "archived": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Archive the session with true or restore it with false. Repeating the current state is a no-op."
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Update session"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "update"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/agent": {
+ "post": {
+ "operationId": "session.switchAgent",
+ "tags": [
+ "session"
+ ],
+ "summary": "Switch session agent",
+ "description": "Switch the agent used by subsequent provider turns.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "agent": {
+ "$ref": "#/components/schemas/Agent.ID"
+ }
+ },
+ "required": [
+ "agent"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Switch session agent"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "switchAgent"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/background": {
+ "post": {
+ "operationId": "session.background",
+ "tags": [
+ "session"
+ ],
+ "summary": "Background blocking session tools",
+ "description": "Move active foreground backgroundable tools for this session into background observation. Idle requests are a no-op.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Background blocking session tools"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "background"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/command": {
+ "post": {
+ "operationId": "session.command",
+ "tags": [
+ "session"
+ ],
+ "summary": "Run command",
+ "description": "Execute a slash command callback immediately.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "text": {
+ "type": "string"
+ },
+ "files": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PromptInput.FileAttachment"
+ }
+ },
+ "agents": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Prompt.AgentAttachment"
+ }
+ },
+ "skills": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PromptInput.SkillAttachment"
+ }
+ },
+ "delivery": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Inbox.Delivery"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "name",
+ "text"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Run command"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, CommandNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/CommandNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "CommandExecutionError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CommandExecutionErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "command"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/compact": {
+ "post": {
+ "operationId": "session.compact",
+ "tags": [
+ "session"
+ ],
+ "summary": "Compact session",
+ "description": "Durably admit a session compaction request. Steers by default: it runs at the next step boundary instead of waiting behind queued prompts.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "delivery": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Inbox.Delivery"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Compact session",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/Session.Inbox.Compaction"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "ConflictError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ConflictErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "compact"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/context": {
+ "get": {
+ "operationId": "session.context",
+ "tags": [
+ "session"
+ ],
+ "summary": "Get session context",
+ "description": "Retrieve the active context messages for a session (all messages after the last compaction).",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Get session context",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Session.Message.Info"
+ }
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "UnknownError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnknownErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "context"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/diff": {
+ "get": {
+ "operationId": "session.diff",
+ "tags": [
+ "session"
+ ],
+ "summary": "Diff session turns",
+ "description": "Structured per-file diffs of the files a turn changed. A turn runs from the first prompt after the session was last idle until its next idle marker, so prompts steered in while it was busy belong to the same turn; `to` extends the range through a later turn. Compares the range's first recorded snapshot with its last; a step still running in the active session compares against the working copy. Ranges that span a location change are rejected. In sessions without any idle marker, a prompt's turn spans until the next user message.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ {
+ "name": "from",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "User message whose turn to diff. Defaults to the turn of the newest user message."
+ }
+ },
+ {
+ "name": "to",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Later user message whose turn ends the range. Defaults to the turn of `from` alone."
+ }
+ },
+ {
+ "name": "context",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 0
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Unchanged lines around each hunk. Omit for full-file patches."
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Diff session turns",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FileDiff.Info"
+ }
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "MessageNotFoundError, SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/MessageNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "UnknownError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnknownErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "diff"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/environment": {
+ "put": {
+ "operationId": "session.environment",
+ "tags": [
+ "session"
+ ],
+ "summary": "Set session environment",
+ "description": "Replace the process environment used by local shell commands for this session.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "variables": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "variables"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Set session environment"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "environment"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/fork": {
+ "post": {
+ "operationId": "session.fork",
+ "tags": [
+ "session"
+ ],
+ "summary": "Fork session",
+ "description": "Create a child session by copying projected history before a message. Omit before to copy the full history.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "before": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Fork session",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/Session.Info"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, MessageNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/MessageNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "fork"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/form": {
+ "get": {
+ "operationId": "session.form.list",
+ "tags": [
+ "session"
+ ],
+ "summary": "List session forms",
+ "description": "Retrieve pending forms for a session.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List session forms",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Form.Info"
+ }
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "form",
+ "list"
+ ]
+ },
+ "post": {
+ "operationId": "session.form.create",
+ "tags": [
+ "session"
+ ],
+ "summary": "Create session form",
+ "description": "Create a form for a session.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Form.ID"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "title": {
+ "type": "string"
+ },
+ "metadata": {
+ "$ref": "#/components/schemas/Form.Metadata"
+ },
+ "fields": {
+ "$ref": "#/components/schemas/Form.Fields"
+ }
+ },
+ "required": [
+ "title",
+ "fields"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Create session form",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/Form.Info"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "ConflictError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ConflictErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "form",
+ "create"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/form/{formID}": {
+ "get": {
+ "operationId": "session.form.get",
+ "tags": [
+ "session"
+ ],
+ "summary": "Get session form",
+ "description": "Retrieve a form and its current state for a session.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "formID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Form.ID"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Get session form",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/Form.Detail"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, FormNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/FormNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "form",
+ "get"
+ ]
+ },
+ "delete": {
+ "operationId": "session.form.cancel",
+ "tags": [
+ "session"
+ ],
+ "summary": "Cancel form",
+ "description": "Cancel a pending form, optionally telling the asker why it was not answered.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "formID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Form.ID"
+ }
+ },
+ {
+ "name": "message",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Cancel form"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, FormNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/FormNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "FormAlreadySettledError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FormAlreadySettledErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "form",
+ "cancel"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/form/{formID}/reply": {
+ "post": {
+ "operationId": "session.form.reply",
+ "tags": [
+ "session"
+ ],
+ "summary": "Reply to form",
+ "description": "Submit an answer to a pending form.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "formID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Form.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "answer": {
+ "$ref": "#/components/schemas/Form.Answer"
+ }
+ },
+ "required": [
+ "answer"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Reply to form"
+ },
+ "400": {
+ "description": "FormInvalidAnswerError, InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/FormInvalidAnswerErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, FormNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/FormNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "FormAlreadySettledError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/FormAlreadySettledErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "form",
+ "reply"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/generate": {
+ "post": {
+ "operationId": "session.generate",
+ "tags": [
+ "session"
+ ],
+ "summary": "Generate text from session context",
+ "description": "Generate transient text from the current session context without mutating session history.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "prompt": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "prompt"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Generate text from session context",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionGenerateResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "generate"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/inbox": {
+ "get": {
+ "operationId": "session.inbox.list",
+ "tags": [
+ "session"
+ ],
+ "summary": "List session inbox",
+ "description": "List durable enqueued session work not yet delivered, ordered by enqueue sequence. Includes user, synthetic, compaction, and move items.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List session inbox",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Session.Inbox.Info"
+ }
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "inbox",
+ "list"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/inbox/{inboxID}": {
+ "delete": {
+ "operationId": "session.inbox.cancel",
+ "tags": [
+ "session"
+ ],
+ "summary": "Cancel inbox input",
+ "description": "Cancel an inbox item that has not yet been delivered. Unavailable items are a no-op.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ {
+ "name": "inboxID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Cancel inbox input"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "inbox",
+ "cancel"
+ ]
+ },
+ "patch": {
+ "operationId": "session.inbox.update",
+ "tags": [
+ "session"
+ ],
+ "summary": "Update inbox item",
+ "description": "Change a pending inbox item's delivery mode. Steering wakes session execution.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ {
+ "name": "inboxID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "delivery": {
+ "$ref": "#/components/schemas/Session.Inbox.Delivery"
+ }
+ },
+ "required": [
+ "delivery"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Update inbox item"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "ConflictError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ConflictErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "inbox",
+ "update"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/interrupt": {
+ "post": {
+ "operationId": "session.interrupt",
+ "tags": [
+ "session"
+ ],
+ "summary": "Interrupt session execution",
+ "description": "Interrupt active execution owned by this OpenCode process. Returns interrupted=true when an active execution was interrupted and false for the idle no-op. When resume=true, execution resumes pending steering input and next-in-line control items (manual compaction, moves) while queued prompts remain parked.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ {
+ "name": "resume",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Interrupt session execution",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionInterruptResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "interrupt"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/message": {
+ "get": {
+ "operationId": "session.message.list",
+ "tags": [
+ "message"
+ ],
+ "summary": "Get session messages",
+ "description": "Retrieve projected messages for a session, optionally filtered by type. Items keep the requested order across pages; use cursor.next or cursor.previous to move through the ordered timeline, passing the same type filter on each page.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 200
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Maximum number of messages to return. When omitted, the endpoint returns its default page size."
+ }
+ },
+ {
+ "name": "order",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "asc",
+ "desc"
+ ]
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Message order for the first page. Use desc for newest first or asc for oldest first."
+ }
+ },
+ {
+ "name": "cursor",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string",
+ "description": "Opaque pagination cursor returned as cursor.previous or cursor.next in the previous response. Do not combine with order."
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ {
+ "name": "type",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "agent-switched",
+ "model-switched",
+ "location-switched",
+ "user",
+ "synthetic",
+ "system",
+ "skill",
+ "shell",
+ "assistant",
+ "compaction",
+ "idle"
+ ]
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Filter by message type before pagination. When omitted, all message types are returned. Pass the same type when following cursors."
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Get session messages",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionMessagesResponse"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidCursorError, InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/InvalidCursorErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "UnknownError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnknownErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/message/{messageID}": {
+ "get": {
+ "operationId": "session.message.get",
+ "tags": [
+ "session"
+ ],
+ "summary": "Get session message",
+ "description": "Retrieve one projected message owned by the Session.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ {
+ "name": "messageID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Get session message",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/Session.Message.Info"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, MessageNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/MessageNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "message",
+ "get"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/model": {
+ "post": {
+ "operationId": "session.switchModel",
+ "tags": [
+ "session"
+ ],
+ "summary": "Switch session model",
+ "description": "Switch the model used by subsequent provider turns.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "model": {
+ "$ref": "#/components/schemas/Model.Ref"
+ }
+ },
+ "required": [
+ "model"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Switch session model"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "switchModel"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/move": {
+ "post": {
+ "operationId": "session.move",
+ "tags": [
+ "session"
+ ],
+ "summary": "Move session",
+ "description": "Move a session to another project directory at the requested delivery boundary.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ },
+ "delivery": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Inbox.Delivery"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "directory"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Move session"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "move"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/permission": {
+ "post": {
+ "operationId": "session.permission.create",
+ "tags": [
+ "permission"
+ ],
+ "summary": "Create permission request",
+ "description": "Evaluate and, when approval is required, create a permission request for a session.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Permission.ID"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "action": {
+ "type": "string"
+ },
+ "resources": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "save": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "source": {
+ "$ref": "#/components/schemas/Permission.Source"
+ },
+ "agent": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Agent.ID"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "action",
+ "resources"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Create permission request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Permission.ID"
+ },
+ "effect": {
+ "$ref": "#/components/schemas/Permission.Effect"
+ }
+ },
+ "required": [
+ "id",
+ "effect"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "create"
+ ]
+ },
+ "get": {
+ "operationId": "session.permission.list",
+ "tags": [
+ "permission"
+ ],
+ "summary": "List session permission requests",
+ "description": "Retrieve pending permission requests owned by a session.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List session permission requests",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Permission.Request"
+ }
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/permission/{requestID}": {
+ "get": {
+ "operationId": "session.permission.get",
+ "tags": [
+ "permission"
+ ],
+ "summary": "Get permission request",
+ "description": "Retrieve a pending permission request owned by a session.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ {
+ "name": "requestID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Permission.ID"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Get permission request",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/Permission.Request"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, PermissionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/PermissionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "get"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/permission/{requestID}/reply": {
+ "post": {
+ "operationId": "session.permission.reply",
+ "tags": [
+ "permission"
+ ],
+ "summary": "Reply to pending permission request",
+ "description": "Respond to a pending permission request owned by a session.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ {
+ "name": "requestID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Permission.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "decision": {
+ "$ref": "#/components/schemas/Permission.Reply"
+ },
+ "message": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "decision"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Reply to pending permission request"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, PermissionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/PermissionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "reply"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/prompt": {
+ "post": {
+ "operationId": "session.prompt",
+ "tags": [
+ "session"
+ ],
+ "summary": "Send message",
+ "description": "Durably admit one session input and schedule agent-loop execution unless resume is false.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "text": {
+ "type": "string"
+ },
+ "files": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PromptInput.FileAttachment"
+ }
+ },
+ "agents": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Prompt.AgentAttachment"
+ }
+ },
+ "skills": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/PromptInput.SkillAttachment"
+ }
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "delivery": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Inbox.Delivery"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "resume": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "text"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Send message",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/Session.Inbox.User"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "ConflictError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ConflictErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "prompt"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/revert": {
+ "delete": {
+ "operationId": "session.revert.clear",
+ "tags": [
+ "session"
+ ],
+ "summary": "Clear staged revert",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Clear staged revert"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "SessionBusyError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionBusyErrorEncoded"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "UnknownError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnknownErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "revert",
+ "clear"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/revert/commit": {
+ "post": {
+ "operationId": "session.revert.commit",
+ "tags": [
+ "session"
+ ],
+ "summary": "Commit staged revert",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Commit staged revert"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "SessionBusyError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionBusyErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "revert",
+ "commit"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/revert/stage": {
+ "post": {
+ "operationId": "session.revert.stage",
+ "tags": [
+ "session"
+ ],
+ "summary": "Stage session revert",
+ "description": "Stage or move a reversible session boundary and optionally apply its file changes.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "messageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "files": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "messageID"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Stage session revert",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/Session.Revert"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "MessageNotFoundError, SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/MessageNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "SessionBusyError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionBusyErrorEncoded"
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "UnknownError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnknownErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "revert",
+ "stage"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/shell": {
+ "post": {
+ "operationId": "session.shell",
+ "tags": [
+ "session"
+ ],
+ "summary": "Run shell command",
+ "description": "Execute one shell command in the session's working directory. Emits a shell.started event before execution and a shell.ended event with the merged output after.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "command": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "command"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Run shell command"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "shell"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/synthetic": {
+ "post": {
+ "operationId": "session.synthetic",
+ "tags": [
+ "session"
+ ],
+ "summary": "Add synthetic message",
+ "description": "Durably admit synthetic session input and schedule execution unless resume is false.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "text": {
+ "type": "string"
+ },
+ "description": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "delivery": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Inbox.Delivery"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "resume": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "text"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Add synthetic message",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/Session.Inbox.Synthetic"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "ConflictError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ConflictErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "synthetic"
+ ]
+ }
+ },
+ "/api/session/{sessionID}/view": {
+ "post": {
+ "operationId": "session.view",
+ "tags": [
+ "session"
+ ],
+ "summary": "View session",
+ "description": "Mark the idle transition observed by the viewer as viewed.",
+ "parameters": [
+ {
+ "name": "sessionID",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "idle": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "idle"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "View session"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "SessionNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SessionNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "view"
+ ]
+ }
+ },
+ "/api/shell": {
+ "get": {
+ "operationId": "shell.list",
+ "tags": [
+ "shell"
+ ],
+ "summary": "List running shell commands",
+ "description": "List currently running shell commands for a location. Exited commands are not included.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List running shell commands",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Shell.Info"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ },
+ "post": {
+ "operationId": "shell.create",
+ "tags": [
+ "shell"
+ ],
+ "summary": "Run shell command",
+ "description": "Spawn one non-interactive shell command for a location. Combined stdout/stderr is captured to a file pageable via output.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "command": {
+ "type": "string"
+ },
+ "cwd": {
+ "type": "string"
+ },
+ "timeout": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "metadata": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "command"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Run shell command",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Shell.Info"
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "create"
+ ]
+ }
+ },
+ "/api/shell/{id}": {
+ "get": {
+ "operationId": "shell.get",
+ "tags": [
+ "shell"
+ ],
+ "summary": "Get shell command",
+ "description": "Get one shell command, including its status and exit code once exited.",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Shell.ID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Get shell command",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Shell.Info"
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "ShellNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/ShellNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "get"
+ ]
+ },
+ "delete": {
+ "operationId": "shell.remove",
+ "tags": [
+ "shell"
+ ],
+ "summary": "Remove shell command",
+ "description": "Terminate and remove one shell command and its retained output.",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Shell.ID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Remove shell command"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "remove"
+ ]
+ }
+ },
+ "/api/shell/{id}/output": {
+ "get": {
+ "operationId": "shell.output",
+ "tags": [
+ "shell"
+ ],
+ "summary": "Read shell output",
+ "description": "Page through captured combined output by absolute byte cursor.",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Shell.ID"
+ }
+ },
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ },
+ {
+ "name": "cursor",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 0
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 0
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Read shell output",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "output": {
+ "type": "string"
+ },
+ "cursor": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "size": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "truncated": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "output",
+ "cursor",
+ "size",
+ "truncated"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "ShellNotFoundError, LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/ShellNotFoundErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "output"
+ ]
+ }
+ },
+ "/api/skill": {
+ "get": {
+ "operationId": "skill.list",
+ "tags": [
+ "skill"
+ ],
+ "summary": "List skills",
+ "description": "Retrieve currently registered skills.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List skills",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Skill.Info"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ }
+ },
+ "/api/vcs": {
+ "get": {
+ "operationId": "vcs.get",
+ "tags": [
+ "vcs"
+ ],
+ "summary": "VCS info",
+ "description": "Get current and default branch information for the requested location.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "VCS info",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Vcs.Info"
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "get"
+ ]
+ }
+ },
+ "/api/vcs/base": {
+ "get": {
+ "operationId": "vcs.base",
+ "tags": [
+ "vcs"
+ ],
+ "summary": "VCS review base",
+ "description": "Infer a local review base from named branch creation history, or the repository default only when currently on that branch. Returns null before the first commit or when the provider lacks base metadata; ambiguous Git history requires an explicit base on diff requests.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "VCS review base",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Vcs.Base"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "base"
+ ]
+ }
+ },
+ "/api/vcs/branch": {
+ "get": {
+ "operationId": "vcs.branch.list",
+ "tags": [
+ "vcs"
+ ],
+ "summary": "VCS branches",
+ "description": "List local and remote branches available at the requested location.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ },
+ {
+ "name": "search",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "VCS branches",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Vcs.BranchList"
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "branch",
+ "list"
+ ]
+ }
+ },
+ "/api/vcs/diff": {
+ "get": {
+ "operationId": "vcs.diff",
+ "tags": [
+ "vcs"
+ ],
+ "summary": "VCS diff",
+ "description": "Diff HEAD to the working copy (working), the base merge-base to the working copy (branch), or the base merge-base to HEAD (committed). Omitting base preserves repository-default comparison; supplying it overrides the comparison without saving it.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ },
+ {
+ "name": "mode",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Vcs.Mode"
+ }
+ },
+ {
+ "name": "base",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ {
+ "name": "context",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "minimum": 0
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "VCS diff",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FileDiff.Info"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "diff"
+ ]
+ }
+ },
+ "/api/vcs/init": {
+ "post": {
+ "operationId": "vcs.init",
+ "tags": [
+ "vcs"
+ ],
+ "summary": "Initialize VCS repository",
+ "description": "Initialize a repository using the selected VCS provider in a markerless project's directory and refresh its location services. Omitting provider defaults to git; built-in git and hg providers support initialization. An unknown provider returns 400; a registered provider without init returns 501.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ },
+ {
+ "name": "provider",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Initialize VCS repository"
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "ConflictError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ConflictErrorEncoded"
+ }
+ }
+ }
+ },
+ "501": {
+ "description": "VcsInitNotSupportedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VcsInitNotSupportedErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "init"
+ ]
+ }
+ },
+ "/api/vcs/status": {
+ "get": {
+ "operationId": "vcs.status",
+ "tags": [
+ "vcs"
+ ],
+ "summary": "VCS status",
+ "description": "List uncommitted working-copy changes relative to the requested location.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "VCS status",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Vcs.FileStatus"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "status"
+ ]
+ }
+ },
+ "/api/websearch": {
+ "post": {
+ "operationId": "websearch.query",
+ "tags": [
+ "websearch"
+ ],
+ "summary": "Search the web",
+ "description": "Run one web search through the selected provider. Specify a provider to override the configured default.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string"
+ },
+ "providerID": {
+ "$ref": "#/components/schemas/WebSearch.ID"
+ }
+ },
+ "required": [
+ "query"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Search the web",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "$ref": "#/components/schemas/WebSearch.ResponseEncoded"
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "query"
+ ]
+ }
+ },
+ "/api/websearch/provider": {
+ "get": {
+ "operationId": "websearch.providers",
+ "tags": [
+ "websearch"
+ ],
+ "summary": "List web search providers",
+ "description": "Return the registered web search providers.",
+ "parameters": [
+ {
+ "name": "location",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "style": "form",
+ "explode": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List web search providers",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/WebSearch.Provider"
+ }
+ }
+ },
+ "required": [
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "LocationNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/LocationNotFoundErrorEncoded"
+ }
+ }
+ }
+ },
+ "503": {
+ "description": "ServiceUnavailableError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ServiceUnavailableErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "providers"
+ ]
+ }
+ },
+ "/api/worktree": {
+ "get": {
+ "operationId": "worktree.list",
+ "tags": [
+ "worktree"
+ ],
+ "summary": "List worktrees",
+ "description": "Return the project's saved worktree inventory without loading configuration or running discovery.",
+ "parameters": [
+ {
+ "name": "projectID",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/Project.ID"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List worktrees",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Worktree.List"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "ProjectNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ProjectNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "list"
+ ]
+ },
+ "post": {
+ "operationId": "worktree.create",
+ "tags": [
+ "worktree"
+ ],
+ "summary": "Create worktree",
+ "description": "Load the project's canonical configuration, create a local worktree using its selected strategy, then run the project's setup script.",
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "projectID": {
+ "$ref": "#/components/schemas/Project.ID"
+ },
+ "from": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ },
+ "branch": {
+ "type": "string"
+ },
+ "directory": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "projectID"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Create worktree",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Worktree.Info"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "WorktreeError, InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/WorktreeErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "ProjectNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ProjectNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "create"
+ ]
+ },
+ "delete": {
+ "operationId": "worktree.remove",
+ "tags": [
+ "worktree"
+ ],
+ "summary": "Remove worktree",
+ "description": "Load the project's canonical configuration and remove a saved worktree using its recorded strategy.",
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "projectID": {
+ "$ref": "#/components/schemas/Project.ID"
+ },
+ "directory": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ },
+ "force": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "projectID",
+ "directory",
+ "force"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Remove worktree"
+ },
+ "400": {
+ "description": "WorktreeError, InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/WorktreeErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "ProjectNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ProjectNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "remove"
+ ]
+ }
+ },
+ "/api/worktree/refresh": {
+ "post": {
+ "operationId": "worktree.refresh",
+ "tags": [
+ "worktree"
+ ],
+ "summary": "Refresh worktrees",
+ "description": "Load the project's canonical configuration, discover worktrees across known checkout roots using all available strategies, and reconcile saved state.",
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "projectID": {
+ "$ref": "#/components/schemas/Project.ID"
+ }
+ },
+ "required": [
+ "projectID"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Refresh worktrees"
+ },
+ "400": {
+ "description": "WorktreeError, InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/WorktreeErrorEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "ProjectNotFoundError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ProjectNotFoundErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "refresh"
+ ]
+ }
+ },
+ "/auth/connect/{code}": {
+ "get": {
+ "operationId": "server.connect",
+ "tags": [
+ "server"
+ ],
+ "summary": "Redeem pairing code",
+ "description": "Redeem a pairing code. Browsers receive a session cookie and a redirect to the web app; requests that accept JSON receive a session token to use as the password.",
+ "parameters": [
+ {
+ "name": "code",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Redeem pairing code",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/PairingSession"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "InvalidRequestError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/InvalidRequestErrorEncoded"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "UnauthorizedError",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UnauthorizedErrorEncoded"
+ }
+ }
+ }
+ }
+ },
+ "x-opencode-client-path": [
+ "connect"
+ ]
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "AbsolutePath": {
+ "type": "string"
+ },
+ "Agent.Color": {
+ "type": "string"
+ },
+ "Agent.ID": {
+ "type": "string"
+ },
+ "Agent.Info": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Agent.ID"
+ },
+ "name": {
+ "type": "string"
+ },
+ "model": {
+ "$ref": "#/components/schemas/Model.Ref"
+ },
+ "request": {
+ "$ref": "#/components/schemas/Provider.Request"
+ },
+ "system": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "mode": {
+ "type": "string",
+ "enum": [
+ "subagent",
+ "primary",
+ "all"
+ ]
+ },
+ "hidden": {
+ "type": "boolean"
+ },
+ "color": {
+ "$ref": "#/components/schemas/Agent.Color"
+ },
+ "steps": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "permissions": {
+ "$ref": "#/components/schemas/Permission.Ruleset"
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "request",
+ "mode",
+ "hidden",
+ "permissions"
+ ],
+ "additionalProperties": true
+ },
+ "AgentNotFoundErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "AgentNotFoundError"
+ ]
+ },
+ "agentID": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "agentID",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "Command.Info": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name"
+ ],
+ "additionalProperties": true
+ },
+ "CommandExecutionErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "CommandExecutionError"
+ ]
+ },
+ "command": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "command",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "CommandNotFoundErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "CommandNotFoundError"
+ ]
+ },
+ "command": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "command",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "Config.AgentEncoded": {
+ "type": "object",
+ "properties": {
+ "model": {
+ "anyOf": [
+ {
+ "type": "string",
+ "pattern": "^[^/#]+\\/[^#]+(?:#[^#]+)?$"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "providerID": {
+ "type": "string",
+ "pattern": "^[^/#]+$"
+ },
+ "model": {
+ "type": "string",
+ "pattern": "^[^#]+$"
+ },
+ "variant": {
+ "type": "string",
+ "pattern": "^[^#]+$"
+ }
+ },
+ "required": [
+ "providerID",
+ "model"
+ ],
+ "additionalProperties": true
+ }
+ ]
+ },
+ "request": {
+ "type": "object",
+ "properties": {
+ "headers": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "body": {
+ "type": "object"
+ }
+ },
+ "additionalProperties": true
+ },
+ "system": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "mode": {
+ "type": "string",
+ "enum": [
+ "subagent",
+ "primary",
+ "all"
+ ]
+ },
+ "hidden": {
+ "type": "boolean"
+ },
+ "color": {
+ "type": "string",
+ "pattern": "^#[0-9a-fA-F]{6}$"
+ },
+ "steps": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "disabled": {
+ "type": "boolean"
+ },
+ "permissions": {
+ "$ref": "#/components/schemas/Permission.Ruleset"
+ }
+ },
+ "additionalProperties": true
+ },
+ "Config.CommandEncoded": {
+ "type": "object",
+ "properties": {
+ "template": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "agent": {
+ "type": "string"
+ },
+ "model": {
+ "anyOf": [
+ {
+ "type": "string",
+ "pattern": "^[^/#]+\\/[^#]+(?:#[^#]+)?$"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "providerID": {
+ "type": "string",
+ "pattern": "^[^/#]+$"
+ },
+ "model": {
+ "type": "string",
+ "pattern": "^[^#]+$"
+ },
+ "variant": {
+ "type": "string",
+ "pattern": "^[^#]+$"
+ }
+ },
+ "required": [
+ "providerID",
+ "model"
+ ],
+ "additionalProperties": true
+ }
+ ]
+ },
+ "subagent": {
+ "type": "boolean"
+ },
+ "subtask": {
+ "type": "boolean",
+ "description": "Deprecated alias for subagent."
+ }
+ },
+ "required": [
+ "template"
+ ],
+ "additionalProperties": true
+ },
+ "Config.DirectoryEncoded": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "directory"
+ ]
+ },
+ "path": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ }
+ },
+ "required": [
+ "type",
+ "path"
+ ],
+ "additionalProperties": true
+ },
+ "Config.DocumentEncoded": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "document"
+ ]
+ },
+ "path": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ },
+ "info": {
+ "$ref": "#/components/schemas/Config.InfoEncoded"
+ }
+ },
+ "required": [
+ "type",
+ "info"
+ ],
+ "additionalProperties": true
+ },
+ "Config.Entry": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Config.DocumentEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/Config.DirectoryEncoded"
+ }
+ ]
+ },
+ "Config.Formatter.EntryEncoded": {
+ "type": "object",
+ "properties": {
+ "disabled": {
+ "type": "boolean"
+ },
+ "command": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "environment": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "extensions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "additionalProperties": true
+ },
+ "Config.InfoEncoded": {
+ "type": "object",
+ "properties": {
+ "shell": {
+ "type": "string"
+ },
+ "model": {
+ "anyOf": [
+ {
+ "type": "string",
+ "pattern": "^[^/#]+\\/[^#]+(?:#[^#]+)?$"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "providerID": {
+ "type": "string",
+ "pattern": "^[^/#]+$"
+ },
+ "model": {
+ "type": "string",
+ "pattern": "^[^#]+$"
+ },
+ "variant": {
+ "type": "string",
+ "pattern": "^[^#]+$"
+ }
+ },
+ "required": [
+ "providerID",
+ "model"
+ ],
+ "additionalProperties": true
+ }
+ ]
+ },
+ "default_agent": {
+ "type": "string"
+ },
+ "update": {
+ "type": "string",
+ "enum": [
+ "disable",
+ "notify",
+ "auto"
+ ]
+ },
+ "share": {
+ "type": "string",
+ "enum": [
+ "manual",
+ "auto",
+ "disabled"
+ ]
+ },
+ "enterprise": {
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": true
+ },
+ "username": {
+ "type": "string"
+ },
+ "permissions": {
+ "$ref": "#/components/schemas/Permission.Ruleset"
+ },
+ "policies": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "action": {
+ "type": "string",
+ "enum": [
+ "provider.use",
+ "tool.use",
+ "integration.use"
+ ]
+ },
+ "resource": {
+ "type": "string"
+ },
+ "effect": {
+ "type": "string",
+ "enum": [
+ "allow",
+ "deny"
+ ]
+ }
+ },
+ "required": [
+ "action",
+ "resource",
+ "effect"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "agents": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/components/schemas/Config.AgentEncoded"
+ }
+ },
+ "snapshots": {
+ "type": "boolean"
+ },
+ "watcher": {
+ "type": "object",
+ "properties": {
+ "ignore": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "additionalProperties": true
+ },
+ "formatter": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/components/schemas/Config.Formatter.EntryEncoded"
+ }
+ }
+ ]
+ },
+ "lsp": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "disabled": {
+ "type": "boolean",
+ "enum": [
+ true
+ ]
+ }
+ },
+ "required": [
+ "disabled"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "$ref": "#/components/schemas/Config.LSP.ServerEncoded"
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "media": {
+ "type": "object",
+ "properties": {
+ "image": {
+ "type": "object",
+ "properties": {
+ "auto_resize": {
+ "type": "boolean"
+ },
+ "max_width": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "max_height": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "max_base64_bytes": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ }
+ },
+ "additionalProperties": true
+ }
+ },
+ "additionalProperties": true
+ },
+ "tool_output": {
+ "type": "object",
+ "properties": {
+ "max_lines": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "max_bytes": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ }
+ },
+ "additionalProperties": true
+ },
+ "mcp": {
+ "type": "object",
+ "properties": {
+ "timeout": {
+ "type": "object",
+ "properties": {
+ "startup": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "catalog": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "execution": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ }
+ },
+ "additionalProperties": true
+ },
+ "servers": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Mcp.LocalConfigEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/Mcp.RemoteConfigEncoded"
+ }
+ ]
+ }
+ }
+ },
+ "additionalProperties": true
+ },
+ "compaction": {
+ "type": "object",
+ "properties": {
+ "auto": {
+ "type": "boolean"
+ },
+ "keep": {
+ "type": "object",
+ "properties": {
+ "tokens": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "additionalProperties": true
+ },
+ "buffer": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "additionalProperties": true
+ },
+ "skills": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "commands": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/components/schemas/Config.CommandEncoded"
+ }
+ },
+ "instructions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "references": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Config.Reference.GitEncoded"
+ },
+ {
+ "$ref": "#/components/schemas/Config.Reference.LocalEncoded"
+ }
+ ]
+ }
+ },
+ "websearch": {
+ "anyOf": [
+ {
+ "type": "boolean",
+ "enum": [
+ false
+ ]
+ },
+ {
+ "$ref": "#/components/schemas/ConfigWebSearch.InfoEncoded"
+ }
+ ]
+ },
+ "plugins": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "$ref": "#/components/schemas/Config.Plugin.EntryEncoded"
+ }
+ ]
+ }
+ },
+ "worktree": {
+ "$ref": "#/components/schemas/Config.Worktree"
+ },
+ "warming": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "$ref": "#/components/schemas/Config.WarmingEncoded"
+ }
+ ]
+ },
+ "providers": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/components/schemas/Config.ProviderEncoded"
+ }
+ },
+ "experimental": {
+ "type": "object",
+ "properties": {
+ "portable_shell_scanner": {
+ "type": "boolean"
+ },
+ "todo": {
+ "type": "boolean"
+ },
+ "subagent_depth": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "location_idle_timeout": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "boolean",
+ "enum": [
+ false
+ ]
+ }
+ ]
+ },
+ "policies": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "action": {
+ "type": "string",
+ "enum": [
+ "provider.use",
+ "tool.use",
+ "integration.use"
+ ]
+ },
+ "resource": {
+ "type": "string"
+ },
+ "effect": {
+ "type": "string",
+ "enum": [
+ "allow",
+ "deny"
+ ]
+ }
+ },
+ "required": [
+ "action",
+ "resource",
+ "effect"
+ ],
+ "additionalProperties": true
+ }
+ }
+ },
+ "additionalProperties": true
+ }
+ },
+ "additionalProperties": true
+ },
+ "Config.LSP.ServerEncoded": {
+ "type": "object",
+ "properties": {
+ "command": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "extensions": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "disabled": {
+ "type": "boolean"
+ },
+ "env": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "initialization": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "command"
+ ],
+ "additionalProperties": true
+ },
+ "Config.Model.Capabilities": {
+ "type": "object",
+ "properties": {
+ "tools": {
+ "type": "boolean"
+ },
+ "input": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "output": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "temperature": {
+ "type": "boolean"
+ },
+ "reasoning": {
+ "$ref": "#/components/schemas/Config.Model.Reasoning"
+ }
+ },
+ "additionalProperties": true
+ },
+ "Config.Model.CostEncoded": {
+ "type": "object",
+ "properties": {
+ "tier": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "context"
+ ]
+ },
+ "size": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "type",
+ "size"
+ ],
+ "additionalProperties": true
+ },
+ "input": {
+ "$ref": "#/components/schemas/Money.USDPerMillionTokens"
+ },
+ "output": {
+ "$ref": "#/components/schemas/Money.USDPerMillionTokens"
+ },
+ "cache": {
+ "type": "object",
+ "properties": {
+ "read": {
+ "$ref": "#/components/schemas/Money.USDPerMillionTokens"
+ },
+ "write": {
+ "$ref": "#/components/schemas/Money.USDPerMillionTokens"
+ }
+ },
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "input",
+ "output"
+ ],
+ "additionalProperties": true
+ },
+ "Config.Model.Reasoning": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "supported": {
+ "type": "boolean",
+ "enum": [
+ false
+ ]
+ }
+ },
+ "required": [
+ "supported"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "supported": {
+ "type": "boolean",
+ "enum": [
+ true
+ ]
+ },
+ "toggle": {
+ "type": "boolean"
+ },
+ "effort": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "budget": {
+ "type": "object",
+ "properties": {
+ "min": {
+ "type": "integer"
+ },
+ "max": {
+ "type": "integer"
+ }
+ },
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "supported"
+ ],
+ "additionalProperties": true
+ }
+ ]
+ },
+ "Config.Model.Settings": {
+ "type": "object",
+ "properties": {
+ "compaction": {
+ "$ref": "#/components/schemas/Provider.Compaction"
+ },
+ "encoding": {
+ "$ref": "#/components/schemas/Provider.Encoding"
+ }
+ },
+ "additionalProperties": {
+ "anyOf": [
+ {},
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "Config.ModelEncoded": {
+ "type": "object",
+ "properties": {
+ "modelID": {
+ "type": "string"
+ },
+ "family": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "releaseDate": {
+ "type": "string",
+ "pattern": "^\\d{4}-\\d{2}(-\\d{2})?$",
+ "description": "Release date as YYYY-MM-DD or YYYY-MM"
+ },
+ "compatibility": {
+ "$ref": "#/components/schemas/Model.Compatibility"
+ },
+ "package": {
+ "type": "string"
+ },
+ "settings": {
+ "$ref": "#/components/schemas/Config.Model.Settings"
+ },
+ "headers": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "body": {
+ "type": "object"
+ },
+ "capabilities": {
+ "$ref": "#/components/schemas/Config.Model.Capabilities"
+ },
+ "variants": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "settings": {
+ "$ref": "#/components/schemas/Config.Model.Settings"
+ },
+ "headers": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "body": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "cost": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Config.Model.CostEncoded"
+ },
+ {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Config.Model.CostEncoded"
+ }
+ }
+ ]
+ },
+ "disabled": {
+ "type": "boolean"
+ },
+ "limit": {
+ "type": "object",
+ "properties": {
+ "context": {
+ "type": "integer"
+ },
+ "input": {
+ "type": "integer"
+ },
+ "output": {
+ "type": "integer"
+ }
+ },
+ "additionalProperties": true
+ }
+ },
+ "additionalProperties": true
+ },
+ "Config.Plugin.EntryEncoded": {
+ "type": "object",
+ "properties": {
+ "package": {
+ "type": "string"
+ },
+ "options": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "package"
+ ],
+ "additionalProperties": true
+ },
+ "Config.Provider.Settings": {
+ "type": "object",
+ "properties": {
+ "timeout": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "boolean",
+ "enum": [
+ false
+ ]
+ }
+ ]
+ },
+ "headerTimeout": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "boolean",
+ "enum": [
+ false
+ ]
+ }
+ ]
+ },
+ "chunkTimeout": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "boolean",
+ "enum": [
+ false
+ ]
+ }
+ ]
+ },
+ "compaction": {
+ "$ref": "#/components/schemas/Provider.Compaction"
+ },
+ "transport": {
+ "$ref": "#/components/schemas/Provider.Transport"
+ },
+ "encoding": {
+ "$ref": "#/components/schemas/Provider.Encoding"
+ }
+ },
+ "additionalProperties": {
+ "anyOf": [
+ {},
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "Config.ProviderEncoded": {
+ "type": "object",
+ "properties": {
+ "canonical": {
+ "$ref": "#/components/schemas/Provider.ID"
+ },
+ "name": {
+ "type": "string"
+ },
+ "env": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "package": {
+ "type": "string"
+ },
+ "compatibility": {
+ "$ref": "#/components/schemas/Provider.Compatibility"
+ },
+ "settings": {
+ "$ref": "#/components/schemas/Config.Provider.Settings"
+ },
+ "headers": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "body": {
+ "type": "object"
+ },
+ "models": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/components/schemas/Config.ModelEncoded"
+ }
+ }
+ },
+ "additionalProperties": true
+ },
+ "Config.Reference.GitEncoded": {
+ "type": "object",
+ "properties": {
+ "repository": {
+ "type": "string"
+ },
+ "branch": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "hidden": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "repository"
+ ],
+ "additionalProperties": true
+ },
+ "Config.Reference.LocalEncoded": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "hidden": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "path"
+ ],
+ "additionalProperties": true
+ },
+ "Config.WarmingEncoded": {
+ "type": "object",
+ "properties": {
+ "prompt": {
+ "type": "string"
+ },
+ "interval": {
+ "type": "string"
+ },
+ "duration": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": true
+ },
+ "Config.Worktree": {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "directory"
+ ],
+ "additionalProperties": true
+ },
+ "ConfigShell.Option": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "acceptable": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "path",
+ "name",
+ "acceptable"
+ ],
+ "additionalProperties": true
+ },
+ "ConfigWebSearch.InfoEncoded": {
+ "type": "object",
+ "properties": {
+ "provider": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "random"
+ ],
+ "description": "Reuse a randomly selected provider until it is rate limited, then switch to another available provider."
+ },
+ {
+ "$ref": "#/components/schemas/WebSearch.ID"
+ }
+ ]
+ }
+ },
+ "required": [
+ "provider"
+ ],
+ "additionalProperties": true
+ },
+ "ConflictErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "ConflictError"
+ ]
+ },
+ "message": {
+ "type": "string"
+ },
+ "resource": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "_tag",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "Connection.CredentialInfo": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "credential"
+ ]
+ },
+ "id": {
+ "$ref": "#/components/schemas/Credential.ID"
+ },
+ "label": {
+ "type": "string"
+ },
+ "method": {
+ "type": "string",
+ "enum": [
+ "key",
+ "oauth",
+ "external"
+ ]
+ },
+ "status": {
+ "$ref": "#/components/schemas/Connection.Status"
+ }
+ },
+ "required": [
+ "type",
+ "id",
+ "label",
+ "method"
+ ],
+ "additionalProperties": true
+ },
+ "Connection.EnvInfo": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "env"
+ ]
+ },
+ "name": {
+ "type": "string"
+ },
+ "status": {
+ "$ref": "#/components/schemas/Connection.Status"
+ }
+ },
+ "required": [
+ "type",
+ "name"
+ ],
+ "additionalProperties": true
+ },
+ "Connection.Info": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Connection.CredentialInfo"
+ },
+ {
+ "$ref": "#/components/schemas/Connection.EnvInfo"
+ }
+ ]
+ },
+ "Connection.Status": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "needs_auth"
+ ]
+ }
+ ]
+ },
+ "message": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "status",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "Credential.Entry": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Credential.ID"
+ },
+ "integrationID": {
+ "$ref": "#/components/schemas/Integration.ID"
+ },
+ "label": {
+ "type": "string"
+ },
+ "active": {
+ "type": "boolean"
+ },
+ "value": {
+ "$ref": "#/components/schemas/Credential.Value"
+ }
+ },
+ "required": [
+ "id",
+ "integrationID",
+ "label",
+ "active",
+ "value"
+ ],
+ "additionalProperties": true
+ },
+ "Credential.External": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "external"
+ ]
+ },
+ "methodID": {
+ "$ref": "#/components/schemas/Integration.MethodID"
+ },
+ "metadata": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "type",
+ "methodID"
+ ],
+ "additionalProperties": true
+ },
+ "Credential.ID": {
+ "type": "string"
+ },
+ "Credential.Key": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "key"
+ ]
+ },
+ "key": {
+ "type": "string"
+ },
+ "metadata": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "type",
+ "key"
+ ],
+ "additionalProperties": true
+ },
+ "Credential.OAuth": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "oauth"
+ ]
+ },
+ "methodID": {
+ "$ref": "#/components/schemas/Integration.MethodID"
+ },
+ "refresh": {
+ "type": "string"
+ },
+ "access": {
+ "type": "string"
+ },
+ "expires": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "metadata": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "type",
+ "methodID",
+ "refresh",
+ "access",
+ "expires"
+ ],
+ "additionalProperties": true
+ },
+ "Credential.Value": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Credential.OAuth"
+ },
+ {
+ "$ref": "#/components/schemas/Credential.Key"
+ },
+ {
+ "$ref": "#/components/schemas/Credential.External"
+ }
+ ]
+ },
+ "Event.ID": {
+ "type": "string",
+ "pattern": "^evt_"
+ },
+ "Event.Seq": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "Event.VersionEncoded": {
+ "type": "number",
+ "enum": [
+ 1
+ ]
+ },
+ "Event.VersionEncoded_10": {
+ "type": "number",
+ "enum": [
+ 2
+ ]
+ },
+ "EventLog.Synced": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "log.synced"
+ ]
+ },
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ }
+ },
+ "required": [
+ "type",
+ "aggregateID"
+ ],
+ "additionalProperties": true,
+ "description": "Marker emitted once when a log read reaches its captured watermark. The reader holds every event committed at or below seq."
+ },
+ "FileDiff.Info": {
+ "type": "object",
+ "properties": {
+ "file": {
+ "type": "string"
+ },
+ "patch": {
+ "type": "string"
+ },
+ "additions": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "deletions": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "added",
+ "deleted",
+ "modified"
+ ]
+ }
+ },
+ "required": [
+ "file",
+ "patch",
+ "additions",
+ "deletions",
+ "status"
+ ],
+ "additionalProperties": true
+ },
+ "FileNotFoundErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "FileNotFoundError"
+ ]
+ },
+ "path": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "path",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "FileSystem.Entry": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "$ref": "#/components/schemas/RelativePath"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "file",
+ "directory"
+ ]
+ }
+ },
+ "required": [
+ "path",
+ "type"
+ ],
+ "additionalProperties": true
+ },
+ "FileSystem.Write": {
+ "type": "object",
+ "properties": {
+ "path": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ }
+ },
+ "required": [
+ "path"
+ ],
+ "additionalProperties": true
+ },
+ "ForbiddenErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "ForbiddenError"
+ ]
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "Form.Answer": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/components/schemas/Form.Value"
+ }
+ },
+ "Form.BooleanField": {
+ "type": "object",
+ "properties": {
+ "key": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "required": {
+ "type": "boolean"
+ },
+ "hidden": {
+ "type": "boolean"
+ },
+ "when": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Form.When"
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "boolean"
+ ]
+ },
+ "default": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "key",
+ "type"
+ ],
+ "additionalProperties": true
+ },
+ "Form.Detail": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Form.ID"
+ },
+ "sessionID": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "metadata": {
+ "$ref": "#/components/schemas/Form.Metadata"
+ },
+ "fields": {
+ "$ref": "#/components/schemas/Form.Fields"
+ },
+ "state": {
+ "$ref": "#/components/schemas/Form.State"
+ }
+ },
+ "required": [
+ "id",
+ "sessionID",
+ "title",
+ "fields",
+ "state"
+ ],
+ "additionalProperties": true
+ },
+ "Form.ExternalField": {
+ "type": "object",
+ "properties": {
+ "key": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "external"
+ ]
+ },
+ "url": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "key",
+ "type",
+ "url"
+ ],
+ "additionalProperties": true
+ },
+ "Form.Field": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Form.StringField"
+ },
+ {
+ "$ref": "#/components/schemas/Form.NumberField"
+ },
+ {
+ "$ref": "#/components/schemas/Form.IntegerField"
+ },
+ {
+ "$ref": "#/components/schemas/Form.BooleanField"
+ },
+ {
+ "$ref": "#/components/schemas/Form.MultiselectField"
+ },
+ {
+ "$ref": "#/components/schemas/Form.ExternalField"
+ }
+ ]
+ },
+ "Form.Fields": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/components/schemas/Form.Field"
+ }
+ },
+ "Form.Fields_1": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/components/schemas/Form.Field"
+ }
+ },
+ "Form.Fields_2": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/components/schemas/Form.Field"
+ }
+ },
+ "Form.Fields_3": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/components/schemas/Form.Field"
+ }
+ },
+ "Form.ID": {
+ "type": "string",
+ "pattern": "^frm_"
+ },
+ "Form.Info": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Form.ID"
+ },
+ "sessionID": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "metadata": {
+ "$ref": "#/components/schemas/Form.Metadata"
+ },
+ "fields": {
+ "$ref": "#/components/schemas/Form.Fields"
+ }
+ },
+ "required": [
+ "id",
+ "sessionID",
+ "title",
+ "fields"
+ ],
+ "additionalProperties": true
+ },
+ "Form.IntegerField": {
+ "type": "object",
+ "properties": {
+ "key": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "required": {
+ "type": "boolean"
+ },
+ "hidden": {
+ "type": "boolean"
+ },
+ "when": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Form.When"
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "integer"
+ ]
+ },
+ "minimum": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ "maximum": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ "default": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ }
+ },
+ "required": [
+ "key",
+ "type"
+ ],
+ "additionalProperties": true
+ },
+ "Form.Metadata": {
+ "type": "object"
+ },
+ "Form.MultiselectField": {
+ "type": "object",
+ "properties": {
+ "key": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "required": {
+ "type": "boolean"
+ },
+ "hidden": {
+ "type": "boolean"
+ },
+ "when": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Form.When"
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "multiselect"
+ ]
+ },
+ "options": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Form.Option"
+ }
+ },
+ "minItems": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "maxItems": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "custom": {
+ "type": "boolean"
+ },
+ "default": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "key",
+ "type",
+ "options"
+ ],
+ "additionalProperties": true
+ },
+ "Form.NumberField": {
+ "type": "object",
+ "properties": {
+ "key": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "required": {
+ "type": "boolean"
+ },
+ "hidden": {
+ "type": "boolean"
+ },
+ "when": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Form.When"
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "number"
+ ]
+ },
+ "minimum": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ "maximum": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ "default": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ }
+ },
+ "required": [
+ "key",
+ "type"
+ ],
+ "additionalProperties": true
+ },
+ "Form.Option": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "type": "string"
+ },
+ "label": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "value",
+ "label"
+ ],
+ "additionalProperties": true
+ },
+ "Form.State": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "pending"
+ ]
+ }
+ },
+ "required": [
+ "status"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "answered"
+ ]
+ },
+ "answer": {
+ "$ref": "#/components/schemas/Form.Answer"
+ }
+ },
+ "required": [
+ "status",
+ "answer"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "cancelled"
+ ]
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "status"
+ ],
+ "additionalProperties": true
+ }
+ ]
+ },
+ "Form.StringField": {
+ "type": "object",
+ "properties": {
+ "key": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "required": {
+ "type": "boolean"
+ },
+ "hidden": {
+ "type": "boolean"
+ },
+ "when": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Form.When"
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "string"
+ ]
+ },
+ "format": {
+ "type": "string",
+ "enum": [
+ "email",
+ "uri",
+ "date",
+ "date-time"
+ ]
+ },
+ "minLength": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "maxLength": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "pattern": {
+ "type": "string"
+ },
+ "placeholder": {
+ "type": "string"
+ },
+ "default": {
+ "type": "string"
+ },
+ "options": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Form.Option"
+ }
+ },
+ "custom": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "key",
+ "type"
+ ],
+ "additionalProperties": true
+ },
+ "Form.Value": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ ]
+ },
+ "Form.When": {
+ "type": "object",
+ "properties": {
+ "key": {
+ "type": "string"
+ },
+ "op": {
+ "type": "string",
+ "enum": [
+ "eq",
+ "neq"
+ ]
+ },
+ "value": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "boolean"
+ }
+ ]
+ }
+ },
+ "required": [
+ "key",
+ "op",
+ "value"
+ ],
+ "additionalProperties": true
+ },
+ "FormAlreadySettledErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "FormAlreadySettledError"
+ ]
+ },
+ "id": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "id",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "FormInvalidAnswerErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "FormInvalidAnswerError"
+ ]
+ },
+ "id": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "id",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "FormNotFoundErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "FormNotFoundError"
+ ]
+ },
+ "id": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "id",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "GenerateTextResponse": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "text": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "text"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "Instruction.Hash": {
+ "type": "string",
+ "pattern": "^[a-f0-9]{64}$"
+ },
+ "Instruction.Key": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9._-]*\\/[a-z0-9][a-z0-9._/-]*$"
+ },
+ "InstructionEntry.Info": {
+ "type": "object",
+ "properties": {
+ "key": {
+ "$ref": "#/components/schemas/InstructionEntry.Key"
+ },
+ "value": {
+ "description": "JSON value attached to the session's instructions"
+ }
+ },
+ "required": [
+ "key",
+ "value"
+ ],
+ "additionalProperties": true
+ },
+ "InstructionEntry.Key": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9._-]*$",
+ "description": "Instruction entry key (lowercase alphanumerics plus . _ -)"
+ },
+ "InstructionEntry.Snapshot": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "key": {
+ "$ref": "#/components/schemas/InstructionEntry.Key"
+ },
+ "value": {
+ "description": "JSON value attached to the session's instructions"
+ },
+ "removed": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "key",
+ "value",
+ "removed"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "InstructionEntryValueTooLargeErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "InstructionEntryValueTooLargeError"
+ ]
+ },
+ "actualBytes": {
+ "type": "integer"
+ },
+ "maxBytes": {
+ "type": "integer"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "actualBytes",
+ "maxBytes",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "Integration.AttemptEncoded": {
+ "type": "object",
+ "properties": {
+ "attemptID": {
+ "$ref": "#/components/schemas/Integration.AttemptID"
+ },
+ "url": {
+ "type": "string"
+ },
+ "instructions": {
+ "type": "string"
+ },
+ "mode": {
+ "type": "string",
+ "enum": [
+ "auto",
+ "code"
+ ]
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ "expires": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ }
+ },
+ "required": [
+ "created",
+ "expires"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "attemptID",
+ "url",
+ "instructions",
+ "mode",
+ "time"
+ ],
+ "additionalProperties": true
+ },
+ "Integration.AttemptID": {
+ "type": "string"
+ },
+ "Integration.AttemptStatus": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "pending"
+ ]
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ "expires": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ }
+ },
+ "required": [
+ "created",
+ "expires"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "status",
+ "time"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "complete"
+ ]
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ "expires": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ }
+ },
+ "required": [
+ "created",
+ "expires"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "status",
+ "time"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "failed"
+ ]
+ },
+ "message": {
+ "type": "string"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ "expires": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ }
+ },
+ "required": [
+ "created",
+ "expires"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "status",
+ "message",
+ "time"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "expired"
+ ]
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ "expires": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ }
+ },
+ "required": [
+ "created",
+ "expires"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "status",
+ "time"
+ ],
+ "additionalProperties": true
+ }
+ ]
+ },
+ "Integration.CommandAttempt": {
+ "type": "object",
+ "properties": {
+ "attemptID": {
+ "$ref": "#/components/schemas/Integration.AttemptID"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ "expires": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ }
+ },
+ "required": [
+ "created",
+ "expires"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "attemptID",
+ "time"
+ ],
+ "additionalProperties": true
+ },
+ "Integration.CommandAttemptStatus": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "pending"
+ ]
+ },
+ "message": {
+ "type": "string"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ "expires": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ }
+ },
+ "required": [
+ "created",
+ "expires"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "status",
+ "time"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "complete"
+ ]
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ "expires": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ }
+ },
+ "required": [
+ "created",
+ "expires"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "status",
+ "time"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "failed"
+ ]
+ },
+ "message": {
+ "type": "string"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ "expires": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ }
+ },
+ "required": [
+ "created",
+ "expires"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "status",
+ "message",
+ "time"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "expired"
+ ]
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ "expires": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ }
+ },
+ "required": [
+ "created",
+ "expires"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "status",
+ "time"
+ ],
+ "additionalProperties": true
+ }
+ ]
+ },
+ "Integration.CommandMethod": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Integration.MethodID"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "command"
+ ]
+ },
+ "label": {
+ "type": "string"
+ },
+ "command": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "type",
+ "label",
+ "command"
+ ],
+ "additionalProperties": true
+ },
+ "Integration.EnvMethod": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "env"
+ ]
+ },
+ "names": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "type",
+ "names"
+ ],
+ "additionalProperties": true
+ },
+ "Integration.ExternalMethod": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Integration.MethodID"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "external"
+ ]
+ },
+ "label": {
+ "type": "string"
+ },
+ "form": {
+ "$ref": "#/components/schemas/Form.Fields_3"
+ }
+ },
+ "required": [
+ "id",
+ "type",
+ "label"
+ ],
+ "additionalProperties": true
+ },
+ "Integration.ID": {
+ "type": "string"
+ },
+ "Integration.Info": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Integration.ID"
+ },
+ "name": {
+ "type": "string"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "methods": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Integration.Method"
+ }
+ },
+ "connections": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Connection.Info"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "methods",
+ "connections"
+ ],
+ "additionalProperties": true
+ },
+ "Integration.KeyMethod": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "key"
+ ]
+ },
+ "label": {
+ "type": "string"
+ },
+ "form": {
+ "$ref": "#/components/schemas/Form.Fields_2"
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "additionalProperties": true
+ },
+ "Integration.Method": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Integration.OAuthMethod"
+ },
+ {
+ "$ref": "#/components/schemas/Integration.CommandMethod"
+ },
+ {
+ "$ref": "#/components/schemas/Integration.KeyMethod"
+ },
+ {
+ "$ref": "#/components/schemas/Integration.ExternalMethod"
+ },
+ {
+ "$ref": "#/components/schemas/Integration.EnvMethod"
+ }
+ ]
+ },
+ "Integration.MethodID": {
+ "type": "string"
+ },
+ "Integration.OAuthMethod": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Integration.MethodID"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "oauth"
+ ]
+ },
+ "label": {
+ "type": "string"
+ },
+ "form": {
+ "$ref": "#/components/schemas/Form.Fields_1"
+ }
+ },
+ "required": [
+ "id",
+ "type",
+ "label"
+ ],
+ "additionalProperties": true
+ },
+ "IntegrationAttemptNotFoundErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "IntegrationAttemptNotFoundError"
+ ]
+ },
+ "integrationID": {
+ "type": "string"
+ },
+ "attemptID": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "integrationID",
+ "attemptID",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "IntegrationMethodNotFoundErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "IntegrationMethodNotFoundError"
+ ]
+ },
+ "integrationID": {
+ "type": "string"
+ },
+ "methodID": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "integrationID",
+ "methodID",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "IntegrationNotFoundErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "IntegrationNotFoundError"
+ ]
+ },
+ "integrationID": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "integrationID",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "InvalidCursorErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "InvalidCursorError"
+ ]
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "InvalidRequestErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "InvalidRequestError"
+ ]
+ },
+ "message": {
+ "type": "string"
+ },
+ "kind": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "field": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "_tag",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "Location.PublicInfo": {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ },
+ "project": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Project.ID"
+ },
+ "directory": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ },
+ "canonical": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ }
+ },
+ "required": [
+ "id",
+ "directory",
+ "canonical"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "directory",
+ "project"
+ ],
+ "additionalProperties": true
+ },
+ "Location.PublicRef": {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ }
+ },
+ "required": [
+ "directory"
+ ],
+ "additionalProperties": true
+ },
+ "Location.Ref": {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ },
+ "workspaceID": {
+ "$ref": "#/components/schemas/Workspace.ID"
+ }
+ },
+ "required": [
+ "directory"
+ ],
+ "additionalProperties": true
+ },
+ "LocationNotFoundErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "LocationNotFoundError"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "location",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "Mcp.LocalConfigEncoded": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "local"
+ ]
+ },
+ "command": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "cwd": {
+ "type": "string"
+ },
+ "environment": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "disabled": {
+ "type": "boolean"
+ },
+ "codemode": {
+ "type": "boolean"
+ },
+ "timeout": {
+ "type": "object",
+ "properties": {
+ "startup": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "catalog": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "execution": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ }
+ },
+ "additionalProperties": true
+ },
+ "protocol": {
+ "$ref": "#/components/schemas/Mcp.Protocol"
+ }
+ },
+ "required": [
+ "type",
+ "command"
+ ],
+ "additionalProperties": true
+ },
+ "Mcp.OAuthConfigEncoded": {
+ "type": "object",
+ "properties": {
+ "client_id": {
+ "type": "string"
+ },
+ "client_secret": {
+ "type": "string"
+ },
+ "scope": {
+ "type": "string"
+ },
+ "callback_port": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 65535
+ },
+ "redirect_uri": {
+ "type": "string"
+ },
+ "auth_server_metadata_url": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": true
+ },
+ "Mcp.Protocol": {
+ "type": "string",
+ "enum": [
+ "legacy",
+ "auto",
+ "2026-07-28"
+ ],
+ "description": "MCP protocol negotiation. \"legacy\" (default) opens with the initialize handshake and speaks protocol revisions up to 2025-11-25. \"auto\" probes for the 2026-07-28 revision and falls back to legacy when the server does not support it. \"2026-07-28\" requires that revision and fails otherwise."
+ },
+ "Mcp.RemoteConfigEncoded": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "remote"
+ ]
+ },
+ "url": {
+ "type": "string"
+ },
+ "headers": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "oauth": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Mcp.OAuthConfigEncoded"
+ },
+ {
+ "type": "boolean",
+ "enum": [
+ false
+ ]
+ }
+ ]
+ },
+ "disabled": {
+ "type": "boolean"
+ },
+ "codemode": {
+ "type": "boolean"
+ },
+ "timeout": {
+ "type": "object",
+ "properties": {
+ "startup": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "catalog": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "execution": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ }
+ },
+ "additionalProperties": true
+ },
+ "protocol": {
+ "$ref": "#/components/schemas/Mcp.Protocol"
+ }
+ },
+ "required": [
+ "type",
+ "url"
+ ],
+ "additionalProperties": true
+ },
+ "Mcp.Resource": {
+ "type": "object",
+ "properties": {
+ "server": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "uri": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "mimeType": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "server",
+ "name",
+ "uri"
+ ],
+ "additionalProperties": true
+ },
+ "Mcp.ResourceCatalog": {
+ "type": "object",
+ "properties": {
+ "resources": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Mcp.Resource"
+ }
+ },
+ "templates": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Mcp.ResourceTemplate"
+ }
+ }
+ },
+ "required": [
+ "resources",
+ "templates"
+ ],
+ "additionalProperties": true
+ },
+ "Mcp.ResourceTemplate": {
+ "type": "object",
+ "properties": {
+ "server": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "uriTemplate": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "mimeType": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "server",
+ "name",
+ "uriTemplate"
+ ],
+ "additionalProperties": true
+ },
+ "Mcp.Server": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "status": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Mcp.Status.Connected"
+ },
+ {
+ "$ref": "#/components/schemas/Mcp.Status.Pending"
+ },
+ {
+ "$ref": "#/components/schemas/Mcp.Status.Disabled"
+ },
+ {
+ "$ref": "#/components/schemas/Mcp.Status.Failed"
+ },
+ {
+ "$ref": "#/components/schemas/Mcp.Status.NeedsAuth"
+ }
+ ]
+ },
+ "integrationID": {
+ "$ref": "#/components/schemas/Integration.ID"
+ }
+ },
+ "required": [
+ "name",
+ "status"
+ ],
+ "additionalProperties": true
+ },
+ "Mcp.Status.Connected": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "connected"
+ ]
+ }
+ },
+ "required": [
+ "status"
+ ],
+ "additionalProperties": true
+ },
+ "Mcp.Status.Disabled": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "disabled"
+ ]
+ }
+ },
+ "required": [
+ "status"
+ ],
+ "additionalProperties": true
+ },
+ "Mcp.Status.Failed": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "failed"
+ ]
+ },
+ "error": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "status",
+ "error"
+ ],
+ "additionalProperties": true
+ },
+ "Mcp.Status.NeedsAuth": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "needs_auth"
+ ]
+ },
+ "error": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "status",
+ "error"
+ ],
+ "additionalProperties": true
+ },
+ "Mcp.Status.Pending": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "pending"
+ ]
+ }
+ },
+ "required": [
+ "status"
+ ],
+ "additionalProperties": true
+ },
+ "McpServerNotFoundErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "McpServerNotFoundError"
+ ]
+ },
+ "server": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "server",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "MessageNotFoundErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "MessageNotFoundError"
+ ]
+ },
+ "sessionID": {
+ "type": "string"
+ },
+ "messageID": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "sessionID",
+ "messageID",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "Model.CacheFormat": {
+ "type": "string",
+ "enum": [
+ "anthropic",
+ "alibaba"
+ ]
+ },
+ "Model.Capabilities": {
+ "type": "object",
+ "properties": {
+ "tools": {
+ "type": "boolean"
+ },
+ "input": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "output": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "temperature": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "tools",
+ "input",
+ "output"
+ ],
+ "additionalProperties": true
+ },
+ "Model.Compatibility": {
+ "type": "object",
+ "properties": {
+ "reasoningField": {
+ "$ref": "#/components/schemas/Model.ReasoningField"
+ },
+ "requireReasoning": {
+ "type": "boolean"
+ },
+ "maxTokensField": {
+ "$ref": "#/components/schemas/Model.MaxTokensField"
+ },
+ "requireFinishReason": {
+ "type": "boolean"
+ },
+ "requireAssistantAfterTool": {
+ "type": "boolean"
+ },
+ "supportsPromptCacheKey": {
+ "type": "boolean"
+ },
+ "streamUsage": {
+ "type": "boolean"
+ },
+ "cacheFormat": {
+ "$ref": "#/components/schemas/Model.CacheFormat"
+ },
+ "supportsThinkingBlockBinding": {
+ "type": "boolean"
+ },
+ "supportsEffortUpdates": {
+ "type": "boolean"
+ }
+ },
+ "additionalProperties": true
+ },
+ "Model.Cost": {
+ "type": "object",
+ "properties": {
+ "tier": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "context"
+ ]
+ },
+ "size": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "type",
+ "size"
+ ],
+ "additionalProperties": true
+ },
+ "input": {
+ "$ref": "#/components/schemas/Money.USDPerMillionTokens"
+ },
+ "output": {
+ "$ref": "#/components/schemas/Money.USDPerMillionTokens"
+ },
+ "cache": {
+ "type": "object",
+ "properties": {
+ "read": {
+ "$ref": "#/components/schemas/Money.USDPerMillionTokens"
+ },
+ "write": {
+ "$ref": "#/components/schemas/Money.USDPerMillionTokens"
+ }
+ },
+ "required": [
+ "read",
+ "write"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "input",
+ "output",
+ "cache"
+ ],
+ "additionalProperties": true
+ },
+ "Model.Info": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "modelID": {
+ "type": "string"
+ },
+ "providerID": {
+ "$ref": "#/components/schemas/Provider.ID"
+ },
+ "canonical": {
+ "$ref": "#/components/schemas/Provider.ID"
+ },
+ "family": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "compatibility": {
+ "$ref": "#/components/schemas/Model.Compatibility"
+ },
+ "package": {
+ "type": "string"
+ },
+ "settings": {
+ "$ref": "#/components/schemas/Model.Settings"
+ },
+ "headers": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "body": {
+ "type": "object"
+ },
+ "capabilities": {
+ "$ref": "#/components/schemas/Model.Capabilities"
+ },
+ "variants": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Model.Variant"
+ }
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "released": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "released"
+ ],
+ "additionalProperties": true
+ },
+ "cost": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Model.Cost"
+ }
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "alpha",
+ "beta",
+ "deprecated",
+ "active"
+ ]
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "limit": {
+ "type": "object",
+ "properties": {
+ "context": {
+ "type": "integer"
+ },
+ "input": {
+ "type": "integer"
+ },
+ "output": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "context",
+ "output"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "modelID",
+ "providerID",
+ "name",
+ "capabilities",
+ "variants",
+ "time",
+ "cost",
+ "status",
+ "enabled",
+ "limit"
+ ],
+ "additionalProperties": true
+ },
+ "Model.MaxTokensField": {
+ "type": "string",
+ "enum": [
+ "max_completion_tokens",
+ "max_tokens"
+ ]
+ },
+ "Model.ReasoningField": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "reasoning",
+ "reasoning_content",
+ "reasoning_text"
+ ]
+ },
+ {
+ "type": "string"
+ }
+ ]
+ },
+ "Model.Ref": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "providerID": {
+ "$ref": "#/components/schemas/Provider.ID"
+ },
+ "variant": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "providerID"
+ ],
+ "additionalProperties": true
+ },
+ "Model.Settings": {
+ "type": "object",
+ "properties": {
+ "compaction": {
+ "$ref": "#/components/schemas/Provider.Compaction"
+ },
+ "encoding": {
+ "$ref": "#/components/schemas/Provider.Encoding"
+ }
+ },
+ "additionalProperties": {}
+ },
+ "Model.Variant": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "settings": {
+ "$ref": "#/components/schemas/Model.Settings"
+ },
+ "headers": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "body": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": true
+ },
+ "Money.USD": {
+ "type": "number"
+ },
+ "Money.USDPerMillionTokens": {
+ "type": "number"
+ },
+ "PairingCode": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "string"
+ },
+ "expires_in": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "code",
+ "expires_in"
+ ],
+ "additionalProperties": true
+ },
+ "PairingSession": {
+ "type": "object",
+ "properties": {
+ "token": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "token"
+ ],
+ "additionalProperties": true
+ },
+ "Permission.Effect": {
+ "type": "string",
+ "enum": [
+ "allow",
+ "deny",
+ "ask"
+ ]
+ },
+ "Permission.ID": {
+ "type": "string",
+ "pattern": "^per"
+ },
+ "Permission.Reply": {
+ "type": "string",
+ "enum": [
+ "once",
+ "always",
+ "reject"
+ ]
+ },
+ "Permission.Request": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Permission.ID"
+ },
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "action": {
+ "type": "string"
+ },
+ "resources": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "save": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "source": {
+ "$ref": "#/components/schemas/Permission.Source"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "sessionID",
+ "action",
+ "resources"
+ ],
+ "additionalProperties": true
+ },
+ "Permission.Rule": {
+ "type": "object",
+ "properties": {
+ "action": {
+ "type": "string"
+ },
+ "resource": {
+ "type": "string"
+ },
+ "effect": {
+ "$ref": "#/components/schemas/Permission.Effect"
+ }
+ },
+ "required": [
+ "action",
+ "resource",
+ "effect"
+ ],
+ "additionalProperties": true
+ },
+ "Permission.Ruleset": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Permission.Rule"
+ }
+ },
+ "Permission.Source": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "tool"
+ ]
+ },
+ "messageID": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "messageID",
+ "id"
+ ],
+ "additionalProperties": true
+ }
+ ]
+ },
+ "PermissionNotFoundErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "PermissionNotFoundError"
+ ]
+ },
+ "requestID": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "requestID",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "PermissionSaved.ID": {
+ "type": "string"
+ },
+ "PermissionSaved.Info": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/PermissionSaved.ID"
+ },
+ "projectID": {
+ "$ref": "#/components/schemas/Project.ID"
+ },
+ "action": {
+ "type": "string"
+ },
+ "resource": {
+ "type": "string"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ },
+ "updated": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created",
+ "updated"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "projectID",
+ "action",
+ "resource",
+ "time"
+ ],
+ "additionalProperties": true
+ },
+ "PermissionSavedNotFoundErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "PermissionSavedNotFoundError"
+ ]
+ },
+ "id": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "id",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "PersistentPty.Handoff": {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "type": "string"
+ },
+ "instanceID": {
+ "type": "string"
+ },
+ "ticket": {
+ "type": "string"
+ },
+ "expiresAt": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ }
+ },
+ "required": [
+ "directory",
+ "instanceID",
+ "ticket",
+ "expiresAt"
+ ],
+ "additionalProperties": true
+ },
+ "PersistentPty.Info": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Pty.ID"
+ },
+ "title": {
+ "type": "string"
+ },
+ "command": {
+ "type": "string"
+ },
+ "args": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "cwd": {
+ "type": "string"
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "running",
+ "exited"
+ ]
+ },
+ "pid": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "exitCode": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "foregroundProcess": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "size": {
+ "type": "object",
+ "properties": {
+ "cols": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "rows": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ }
+ },
+ "required": [
+ "cols",
+ "rows"
+ ],
+ "additionalProperties": true
+ },
+ "output": {
+ "type": "object",
+ "properties": {
+ "head": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "tail": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "head",
+ "tail"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "title",
+ "command",
+ "args",
+ "cwd",
+ "status",
+ "pid",
+ "sessionID",
+ "foregroundProcess",
+ "size",
+ "output"
+ ],
+ "additionalProperties": true
+ },
+ "PersistentPty.ReadLines": {
+ "type": "integer",
+ "exclusiveMinimum": 0,
+ "maximum": 65535
+ },
+ "PersistentPty.ReadResult": {
+ "type": "object",
+ "properties": {
+ "ptyID": {
+ "$ref": "#/components/schemas/Pty.ID"
+ },
+ "title": {
+ "type": "string"
+ },
+ "cwd": {
+ "type": "string"
+ },
+ "foregroundProcess": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "screen": {
+ "type": "object",
+ "properties": {
+ "text": {
+ "type": "string"
+ },
+ "cols": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "rows": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "cursor": {
+ "type": "object",
+ "properties": {
+ "x": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "y": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "x",
+ "y"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "text",
+ "cols",
+ "rows",
+ "cursor"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "ptyID",
+ "title",
+ "cwd",
+ "foregroundProcess",
+ "screen"
+ ],
+ "additionalProperties": true
+ },
+ "PersistentPty.Snapshot": {
+ "type": "object",
+ "properties": {
+ "info": {
+ "$ref": "#/components/schemas/PersistentPty.Info"
+ },
+ "text": {
+ "type": "string"
+ },
+ "checkpoint": {
+ "type": "string",
+ "format": "byte",
+ "contentEncoding": "base64"
+ },
+ "cursor": {
+ "type": "object",
+ "properties": {
+ "x": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "y": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "x",
+ "y"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "info",
+ "text",
+ "checkpoint",
+ "cursor"
+ ],
+ "additionalProperties": true
+ },
+ "Plugin.Features": {
+ "type": "object",
+ "properties": {
+ "server": {
+ "type": "boolean",
+ "enum": [
+ true
+ ]
+ },
+ "tui": {
+ "type": "boolean",
+ "enum": [
+ true
+ ]
+ },
+ "rpc": {
+ "type": "boolean",
+ "enum": [
+ true
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ "Plugin.Info": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "source": {
+ "$ref": "#/components/schemas/Plugin.Source"
+ },
+ "features": {
+ "$ref": "#/components/schemas/Plugin.Features"
+ },
+ "state": {
+ "$ref": "#/components/schemas/Plugin.State"
+ }
+ },
+ "required": [
+ "source",
+ "features",
+ "state"
+ ],
+ "additionalProperties": true
+ },
+ "Plugin.Source": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "builtin"
+ ]
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "package"
+ ]
+ },
+ "target": {
+ "type": "string"
+ },
+ "version": {
+ "type": "string"
+ },
+ "outdated": {
+ "type": "boolean",
+ "enum": [
+ true
+ ]
+ },
+ "updating": {
+ "type": "boolean",
+ "enum": [
+ true
+ ]
+ }
+ },
+ "required": [
+ "type",
+ "target"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "local"
+ ]
+ },
+ "path": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "path"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "sdk"
+ ]
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "additionalProperties": true
+ }
+ ]
+ },
+ "Plugin.State": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "active"
+ ]
+ }
+ },
+ "required": [
+ "status"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "failed"
+ ]
+ },
+ "error": {
+ "type": "string"
+ },
+ "ref": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "status",
+ "error"
+ ],
+ "additionalProperties": true
+ }
+ ]
+ },
+ "Project": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Project.ID"
+ },
+ "canonical": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ },
+ "vcs": {
+ "$ref": "#/components/schemas/Project.Vcs"
+ },
+ "name": {
+ "type": "string"
+ },
+ "icon": {
+ "$ref": "#/components/schemas/Project.Icon"
+ },
+ "commands": {
+ "$ref": "#/components/schemas/Project.Commands"
+ },
+ "time": {
+ "$ref": "#/components/schemas/Project.Time"
+ },
+ "sandboxes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "canonical",
+ "time",
+ "sandboxes"
+ ],
+ "additionalProperties": true
+ },
+ "Project.Commands": {
+ "type": "object",
+ "properties": {
+ "start": {
+ "type": "string",
+ "description": "Startup script to run when creating a new workspace (worktree)"
+ }
+ },
+ "additionalProperties": true
+ },
+ "Project.ID": {
+ "type": "string"
+ },
+ "Project.Icon": {
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "string"
+ },
+ "override": {
+ "type": "string"
+ },
+ "color": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": true
+ },
+ "Project.Time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "updated": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "active": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "archived": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "created",
+ "updated",
+ "active"
+ ],
+ "additionalProperties": true
+ },
+ "Project.Vcs": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9._-]*$"
+ },
+ "ProjectNotFoundErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "ProjectNotFoundError"
+ ]
+ },
+ "projectID": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "projectID",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "Prompt.AgentAttachment": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "mention": {
+ "$ref": "#/components/schemas/Prompt.Mention"
+ }
+ },
+ "required": [
+ "name"
+ ],
+ "additionalProperties": true
+ },
+ "Prompt.Base64": {
+ "type": "string",
+ "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$"
+ },
+ "Prompt.FileAttachment": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/Prompt.Base64"
+ },
+ "mime": {
+ "type": "string"
+ },
+ "source": {
+ "$ref": "#/components/schemas/Prompt.FileSource"
+ },
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "mention": {
+ "$ref": "#/components/schemas/Prompt.Mention"
+ }
+ },
+ "required": [
+ "data",
+ "mime",
+ "source"
+ ],
+ "additionalProperties": true
+ },
+ "Prompt.FileSource": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "inline"
+ ]
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "uri"
+ ]
+ },
+ "uri": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "uri"
+ ],
+ "additionalProperties": true
+ }
+ ]
+ },
+ "Prompt.Mention": {
+ "type": "object",
+ "properties": {
+ "start": {
+ "type": "number"
+ },
+ "end": {
+ "type": "number"
+ },
+ "text": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "start",
+ "end",
+ "text"
+ ],
+ "additionalProperties": true
+ },
+ "Prompt.SkillAttachment": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Skill.ID"
+ },
+ "name": {
+ "$ref": "#/components/schemas/Skill.Name"
+ },
+ "text": {
+ "type": "string"
+ },
+ "mention": {
+ "$ref": "#/components/schemas/Prompt.Mention"
+ }
+ },
+ "required": [
+ "id",
+ "name"
+ ],
+ "additionalProperties": true
+ },
+ "PromptInput.FileAttachment": {
+ "type": "object",
+ "properties": {
+ "uri": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "mention": {
+ "$ref": "#/components/schemas/Prompt.Mention"
+ }
+ },
+ "required": [
+ "uri"
+ ],
+ "additionalProperties": true
+ },
+ "PromptInput.SkillAttachment": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Skill.ID"
+ },
+ "mention": {
+ "$ref": "#/components/schemas/Prompt.Mention"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": true
+ },
+ "Provider.Compaction": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "summary"
+ ]
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "native"
+ ]
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "additionalProperties": true
+ }
+ ]
+ },
+ "Provider.Compatibility": {
+ "type": "object",
+ "properties": {
+ "reasoningField": {
+ "$ref": "#/components/schemas/Model.ReasoningField"
+ },
+ "requireReasoning": {
+ "type": "boolean"
+ },
+ "maxTokensField": {
+ "$ref": "#/components/schemas/Model.MaxTokensField"
+ },
+ "requireFinishReason": {
+ "type": "boolean"
+ },
+ "requireAssistantAfterTool": {
+ "type": "boolean"
+ },
+ "supportsPromptCacheKey": {
+ "type": "boolean"
+ },
+ "streamUsage": {
+ "type": "boolean"
+ },
+ "cacheFormat": {
+ "$ref": "#/components/schemas/Model.CacheFormat"
+ }
+ },
+ "additionalProperties": true
+ },
+ "Provider.Encoding": {
+ "type": "string",
+ "enum": [
+ "zstd",
+ "gzip",
+ "none"
+ ]
+ },
+ "Provider.ID": {
+ "type": "string"
+ },
+ "Provider.Info": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Provider.ID"
+ },
+ "canonical": {
+ "$ref": "#/components/schemas/Provider.ID"
+ },
+ "integrationID": {
+ "$ref": "#/components/schemas/Integration.ID"
+ },
+ "name": {
+ "type": "string"
+ },
+ "activation": {
+ "type": "string",
+ "enum": [
+ "auto",
+ "enabled",
+ "disabled"
+ ]
+ },
+ "package": {
+ "type": "string"
+ },
+ "compatibility": {
+ "$ref": "#/components/schemas/Provider.Compatibility"
+ },
+ "settings": {
+ "$ref": "#/components/schemas/Provider.Settings"
+ },
+ "headers": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "body": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "activation",
+ "package"
+ ],
+ "additionalProperties": true
+ },
+ "Provider.Request": {
+ "type": "object",
+ "properties": {
+ "settings": {
+ "$ref": "#/components/schemas/Provider.Settings"
+ },
+ "headers": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "body": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "settings",
+ "headers",
+ "body"
+ ],
+ "additionalProperties": true
+ },
+ "Provider.Settings": {
+ "type": "object",
+ "properties": {
+ "timeout": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "boolean",
+ "enum": [
+ false
+ ]
+ }
+ ]
+ },
+ "headerTimeout": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "boolean",
+ "enum": [
+ false
+ ]
+ }
+ ]
+ },
+ "chunkTimeout": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "boolean",
+ "enum": [
+ false
+ ]
+ }
+ ]
+ },
+ "compaction": {
+ "$ref": "#/components/schemas/Provider.Compaction"
+ },
+ "transport": {
+ "$ref": "#/components/schemas/Provider.Transport"
+ },
+ "encoding": {
+ "$ref": "#/components/schemas/Provider.Encoding"
+ }
+ },
+ "additionalProperties": {}
+ },
+ "Provider.Transport": {
+ "type": "string",
+ "enum": [
+ "http",
+ "websocket"
+ ]
+ },
+ "ProviderNotFoundErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "ProviderNotFoundError"
+ ]
+ },
+ "providerID": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "providerID",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "Pty": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Pty.ID"
+ },
+ "title": {
+ "type": "string"
+ },
+ "command": {
+ "type": "string"
+ },
+ "args": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "cwd": {
+ "type": "string"
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "running",
+ "exited"
+ ]
+ },
+ "pid": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "exitCode": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "id",
+ "title",
+ "command",
+ "args",
+ "cwd",
+ "status",
+ "pid"
+ ],
+ "additionalProperties": true
+ },
+ "Pty.ID": {
+ "type": "string",
+ "pattern": "^pty"
+ },
+ "PtyNotFoundErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "PtyNotFoundError"
+ ]
+ },
+ "ptyID": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "ptyID",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "PtyTicket.ConnectToken": {
+ "type": "object",
+ "properties": {
+ "ticket": {
+ "type": "string"
+ },
+ "expires_in": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ }
+ },
+ "required": [
+ "ticket",
+ "expires_in"
+ ],
+ "additionalProperties": true
+ },
+ "Reference.GitSource": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "git"
+ ]
+ },
+ "repository": {
+ "type": "string"
+ },
+ "branch": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "repository"
+ ],
+ "additionalProperties": true
+ },
+ "Reference.Info": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "path": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ },
+ "description": {
+ "type": "string"
+ },
+ "hidden": {
+ "type": "boolean"
+ },
+ "source": {
+ "$ref": "#/components/schemas/Reference.Source"
+ }
+ },
+ "required": [
+ "name",
+ "path",
+ "source"
+ ],
+ "additionalProperties": true
+ },
+ "Reference.LocalSource": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "local"
+ ]
+ },
+ "path": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ }
+ },
+ "required": [
+ "type",
+ "path"
+ ],
+ "additionalProperties": true
+ },
+ "Reference.Source": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Reference.LocalSource"
+ },
+ {
+ "$ref": "#/components/schemas/Reference.GitSource"
+ }
+ ]
+ },
+ "RelativePath": {
+ "type": "string"
+ },
+ "Rpc.Output": {
+ "type": "object",
+ "properties": {
+ "output": {}
+ },
+ "additionalProperties": true
+ },
+ "RpcErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "RpcError"
+ ]
+ },
+ "type": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ },
+ "data": {
+ "anyOf": [
+ {},
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "_tag",
+ "type",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "RpcInternalErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "RpcInternalError"
+ ]
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "rpc.internal",
+ "rpc.invalid_output"
+ ]
+ },
+ "message": {
+ "type": "string"
+ },
+ "data": {
+ "anyOf": [
+ {},
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "_tag",
+ "type",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "ServerInfo": {
+ "type": "object",
+ "properties": {
+ "version": {
+ "type": "string"
+ },
+ "pid": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "urls": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "paths": {
+ "type": "object",
+ "properties": {
+ "tmp": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "tmp"
+ ],
+ "additionalProperties": true
+ },
+ "capabilities": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "persistentPty": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "version",
+ "pid",
+ "urls",
+ "paths"
+ ],
+ "additionalProperties": true
+ },
+ "ServiceUnavailableErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "ServiceUnavailableError"
+ ]
+ },
+ "message": {
+ "type": "string"
+ },
+ "service": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "_tag",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Event.Durable": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/session.created"
+ },
+ {
+ "$ref": "#/components/schemas/session.agent.selected"
+ },
+ {
+ "$ref": "#/components/schemas/session.model.selected"
+ },
+ {
+ "$ref": "#/components/schemas/session.moved"
+ },
+ {
+ "$ref": "#/components/schemas/session.renamed"
+ },
+ {
+ "$ref": "#/components/schemas/session.metadata.updated"
+ },
+ {
+ "$ref": "#/components/schemas/session.permissions"
+ },
+ {
+ "$ref": "#/components/schemas/session.viewed"
+ },
+ {
+ "$ref": "#/components/schemas/session.archived"
+ },
+ {
+ "$ref": "#/components/schemas/session.unarchived"
+ },
+ {
+ "$ref": "#/components/schemas/session.deleted"
+ },
+ {
+ "$ref": "#/components/schemas/session.forked"
+ },
+ {
+ "$ref": "#/components/schemas/session.inbox.delivered"
+ },
+ {
+ "$ref": "#/components/schemas/session.inbox.enqueued"
+ },
+ {
+ "$ref": "#/components/schemas/session.inbox.cancelled"
+ },
+ {
+ "$ref": "#/components/schemas/session.inbox.delivery.changed"
+ },
+ {
+ "$ref": "#/components/schemas/session.execution.started"
+ },
+ {
+ "$ref": "#/components/schemas/session.execution.succeeded"
+ },
+ {
+ "$ref": "#/components/schemas/session.execution.failed"
+ },
+ {
+ "$ref": "#/components/schemas/session.execution.interrupted"
+ },
+ {
+ "$ref": "#/components/schemas/session.instructions.updated"
+ },
+ {
+ "$ref": "#/components/schemas/session.synthetic"
+ },
+ {
+ "$ref": "#/components/schemas/session.skill.activated"
+ },
+ {
+ "$ref": "#/components/schemas/session.shell.started"
+ },
+ {
+ "$ref": "#/components/schemas/session.shell.ended"
+ },
+ {
+ "$ref": "#/components/schemas/session.step.started"
+ },
+ {
+ "$ref": "#/components/schemas/session.step.streamed"
+ },
+ {
+ "$ref": "#/components/schemas/session.step.ended"
+ },
+ {
+ "$ref": "#/components/schemas/session.step.failed"
+ },
+ {
+ "$ref": "#/components/schemas/session.text.started"
+ },
+ {
+ "$ref": "#/components/schemas/session.text.ended"
+ },
+ {
+ "$ref": "#/components/schemas/session.reasoning.started"
+ },
+ {
+ "$ref": "#/components/schemas/session.reasoning.ended"
+ },
+ {
+ "$ref": "#/components/schemas/session.tool.input.started"
+ },
+ {
+ "$ref": "#/components/schemas/session.tool.input.ended"
+ },
+ {
+ "$ref": "#/components/schemas/session.tool.called"
+ },
+ {
+ "$ref": "#/components/schemas/session.tool.success"
+ },
+ {
+ "$ref": "#/components/schemas/session.tool.failed"
+ },
+ {
+ "$ref": "#/components/schemas/session.retry.scheduled"
+ },
+ {
+ "$ref": "#/components/schemas/session.compaction.started"
+ },
+ {
+ "$ref": "#/components/schemas/session.compaction.ended"
+ },
+ {
+ "$ref": "#/components/schemas/session.compaction.failed"
+ },
+ {
+ "$ref": "#/components/schemas/session.revert.staged"
+ },
+ {
+ "$ref": "#/components/schemas/session.revert.cleared"
+ },
+ {
+ "$ref": "#/components/schemas/session.revert.committed"
+ },
+ {
+ "$ref": "#/components/schemas/session.usage.recorded"
+ },
+ {
+ "$ref": "#/components/schemas/session.message.content.updated"
+ }
+ ]
+ },
+ "Session.ForkBoundary": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "before"
+ ]
+ },
+ "messageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ }
+ },
+ "required": [
+ "type",
+ "messageID"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "through"
+ ]
+ },
+ "messageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ }
+ },
+ "required": [
+ "type",
+ "messageID"
+ ],
+ "additionalProperties": true
+ }
+ ]
+ },
+ "Session.ID": {
+ "type": "string",
+ "pattern": "^ses"
+ },
+ "Session.ID_1": {
+ "type": "string",
+ "pattern": "^ses",
+ "description": "Session ID to navigate to"
+ },
+ "Session.Inbox.Compaction": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "compaction"
+ ]
+ },
+ "payload": {
+ "$ref": "#/components/schemas/Session.Inbox.CompactionPayload"
+ },
+ "delivery": {
+ "$ref": "#/components/schemas/Session.Inbox.Delivery"
+ }
+ },
+ "required": [
+ "id",
+ "sessionID",
+ "time",
+ "type",
+ "payload",
+ "delivery"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Inbox.CompactionPayload": {
+ "not": {
+ "type": "null"
+ }
+ },
+ "Session.Inbox.Delivery": {
+ "type": "string",
+ "enum": [
+ "steer",
+ "queue"
+ ]
+ },
+ "Session.Inbox.Info": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Inbox.User"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Inbox.Synthetic"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Inbox.Compaction"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Inbox.Move"
+ }
+ ]
+ },
+ "Session.Inbox.Item": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "user"
+ ]
+ },
+ "payload": {
+ "$ref": "#/components/schemas/Session.Inbox.UserPayload"
+ },
+ "delivery": {
+ "$ref": "#/components/schemas/Session.Inbox.Delivery"
+ }
+ },
+ "required": [
+ "type",
+ "payload",
+ "delivery"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "synthetic"
+ ]
+ },
+ "payload": {
+ "$ref": "#/components/schemas/Session.Inbox.SyntheticPayload"
+ },
+ "delivery": {
+ "$ref": "#/components/schemas/Session.Inbox.Delivery"
+ }
+ },
+ "required": [
+ "type",
+ "payload",
+ "delivery"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "compaction"
+ ]
+ },
+ "payload": {
+ "$ref": "#/components/schemas/Session.Inbox.CompactionPayload"
+ },
+ "delivery": {
+ "$ref": "#/components/schemas/Session.Inbox.Delivery"
+ }
+ },
+ "required": [
+ "type",
+ "payload",
+ "delivery"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "move"
+ ]
+ },
+ "payload": {
+ "$ref": "#/components/schemas/Session.Inbox.MovePayload_1"
+ },
+ "delivery": {
+ "$ref": "#/components/schemas/Session.Inbox.Delivery"
+ }
+ },
+ "required": [
+ "type",
+ "payload",
+ "delivery"
+ ],
+ "additionalProperties": true
+ }
+ ]
+ },
+ "Session.Inbox.Move": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "move"
+ ]
+ },
+ "delivery": {
+ "$ref": "#/components/schemas/Session.Inbox.Delivery"
+ },
+ "payload": {
+ "$ref": "#/components/schemas/Session.Inbox.MovePayload"
+ }
+ },
+ "required": [
+ "id",
+ "sessionID",
+ "time",
+ "type",
+ "delivery",
+ "payload"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Inbox.MovePayload": {
+ "type": "object",
+ "properties": {
+ "projectID": {
+ "$ref": "#/components/schemas/Project.ID"
+ },
+ "subpath": {
+ "$ref": "#/components/schemas/RelativePath"
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ }
+ },
+ "required": [
+ "projectID",
+ "location"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Inbox.MovePayload_1": {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "projectID": {
+ "$ref": "#/components/schemas/Project.ID"
+ },
+ "subpath": {
+ "$ref": "#/components/schemas/RelativePath"
+ }
+ },
+ "required": [
+ "location",
+ "projectID"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Inbox.Synthetic": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "synthetic"
+ ]
+ },
+ "payload": {
+ "$ref": "#/components/schemas/Session.Inbox.SyntheticPayload"
+ },
+ "delivery": {
+ "$ref": "#/components/schemas/Session.Inbox.Delivery"
+ }
+ },
+ "required": [
+ "id",
+ "sessionID",
+ "time",
+ "type",
+ "payload",
+ "delivery"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Inbox.SyntheticPayload": {
+ "type": "object",
+ "properties": {
+ "text": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "metadata": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "text"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Inbox.User": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "user"
+ ]
+ },
+ "payload": {
+ "$ref": "#/components/schemas/Session.Inbox.UserPayload"
+ },
+ "delivery": {
+ "$ref": "#/components/schemas/Session.Inbox.Delivery"
+ }
+ },
+ "required": [
+ "id",
+ "sessionID",
+ "time",
+ "type",
+ "payload",
+ "delivery"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Inbox.UserPayload": {
+ "type": "object",
+ "properties": {
+ "text": {
+ "type": "string"
+ },
+ "files": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Prompt.FileAttachment"
+ }
+ },
+ "agents": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Prompt.AgentAttachment"
+ }
+ },
+ "skills": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Prompt.SkillAttachment"
+ }
+ },
+ "metadata": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "text"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Info": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "parentID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "fork": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "boundary": {
+ "$ref": "#/components/schemas/Session.ForkBoundary"
+ }
+ },
+ "required": [
+ "sessionID",
+ "boundary"
+ ],
+ "additionalProperties": true
+ },
+ "projectID": {
+ "$ref": "#/components/schemas/Project.ID"
+ },
+ "agent": {
+ "$ref": "#/components/schemas/Agent.ID"
+ },
+ "model": {
+ "$ref": "#/components/schemas/Model.Ref"
+ },
+ "cost": {
+ "$ref": "#/components/schemas/Money.USD"
+ },
+ "tokens": {
+ "$ref": "#/components/schemas/TokenUsage.Info"
+ },
+ "outcome": {
+ "type": "string",
+ "enum": [
+ "succeeded",
+ "failed",
+ "interrupted"
+ ]
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ },
+ "updated": {
+ "type": "number"
+ },
+ "idle": {
+ "type": "number"
+ },
+ "viewed": {
+ "type": "number"
+ },
+ "archived": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created",
+ "updated"
+ ],
+ "additionalProperties": true
+ },
+ "title": {
+ "type": "string"
+ },
+ "subpath": {
+ "$ref": "#/components/schemas/RelativePath"
+ },
+ "metadata": {
+ "$ref": "#/components/schemas/Session.Metadata"
+ },
+ "permissions": {
+ "$ref": "#/components/schemas/Permission.Ruleset"
+ },
+ "revert": {
+ "$ref": "#/components/schemas/Session.Revert"
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ }
+ },
+ "required": [
+ "id",
+ "projectID",
+ "cost",
+ "tokens",
+ "time",
+ "location"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.AgentSelected": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "agent-switched"
+ ]
+ },
+ "agent": {
+ "$ref": "#/components/schemas/Agent.ID"
+ },
+ "previous": {
+ "$ref": "#/components/schemas/Agent.ID"
+ }
+ },
+ "required": [
+ "id",
+ "time",
+ "type",
+ "agent"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.Assistant": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ },
+ "streamed": {
+ "type": "number"
+ },
+ "completed": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "assistant"
+ ]
+ },
+ "agent": {
+ "$ref": "#/components/schemas/Agent.ID"
+ },
+ "model": {
+ "$ref": "#/components/schemas/Model.Ref"
+ },
+ "content": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Message.Assistant.Text"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.Assistant.Reasoning"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.Assistant.Tool"
+ }
+ ]
+ }
+ },
+ "snapshot": {
+ "type": "object",
+ "properties": {
+ "start": {
+ "$ref": "#/components/schemas/Snapshot.ID"
+ },
+ "end": {
+ "$ref": "#/components/schemas/Snapshot.ID"
+ },
+ "files": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RelativePath"
+ }
+ }
+ },
+ "additionalProperties": true
+ },
+ "finish": {
+ "type": "string",
+ "enum": [
+ "stop",
+ "length",
+ "tool-calls",
+ "content-filter",
+ "error",
+ "unknown"
+ ]
+ },
+ "rawFinish": {
+ "type": "string"
+ },
+ "native": {
+ "$ref": "#/components/schemas/Session.Message.Native_3"
+ },
+ "cost": {
+ "$ref": "#/components/schemas/Money.USD"
+ },
+ "tokens": {
+ "$ref": "#/components/schemas/TokenUsage.Info"
+ },
+ "error": {
+ "$ref": "#/components/schemas/Session.StructuredError"
+ },
+ "retry": {
+ "$ref": "#/components/schemas/Session.Message.Assistant.Retry"
+ }
+ },
+ "required": [
+ "id",
+ "time",
+ "type",
+ "agent",
+ "model",
+ "content"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.Assistant.Reasoning": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "reasoning"
+ ]
+ },
+ "text": {
+ "type": "string"
+ },
+ "native": {
+ "$ref": "#/components/schemas/Session.Message.Native_1"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ },
+ "completed": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "type",
+ "text"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.Assistant.Reasoning_1": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "reasoning"
+ ]
+ },
+ "text": {
+ "type": "string"
+ },
+ "native": {
+ "$ref": "#/components/schemas/Session.Message.Native_14"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ },
+ "completed": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "type",
+ "text"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.Assistant.Retry": {
+ "type": "object",
+ "properties": {
+ "attempt": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "at": {
+ "type": "number"
+ },
+ "error": {
+ "$ref": "#/components/schemas/Session.StructuredError"
+ }
+ },
+ "required": [
+ "attempt",
+ "at",
+ "error"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.Assistant.Text": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "text"
+ ]
+ },
+ "text": {
+ "type": "string"
+ },
+ "native": {
+ "$ref": "#/components/schemas/Session.Message.Native"
+ }
+ },
+ "required": [
+ "type",
+ "text"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.Assistant.Text_1": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "text"
+ ]
+ },
+ "text": {
+ "type": "string"
+ },
+ "native": {
+ "$ref": "#/components/schemas/Session.Message.Native_13"
+ }
+ },
+ "required": [
+ "type",
+ "text"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.Assistant.Tool": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "tool"
+ ]
+ },
+ "id": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "executed": {
+ "type": "boolean"
+ },
+ "native": {
+ "$ref": "#/components/schemas/Session.Message.Native_2"
+ },
+ "state": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Message.ToolState.Streaming"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.ToolState.Running"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.ToolState.Completed"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.ToolState.Error"
+ }
+ ]
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ },
+ "ran": {
+ "type": "number"
+ },
+ "completed": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "type",
+ "id",
+ "name",
+ "state",
+ "time"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.Assistant.Tool_1": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "tool"
+ ]
+ },
+ "id": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "executed": {
+ "type": "boolean"
+ },
+ "native": {
+ "$ref": "#/components/schemas/Session.Message.Native_15"
+ },
+ "state": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Message.ToolState.Streaming"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.ToolState.Running_1"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.ToolState.Completed_1"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.ToolState.Error_1"
+ }
+ ]
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ },
+ "ran": {
+ "type": "number"
+ },
+ "completed": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "type",
+ "id",
+ "name",
+ "state",
+ "time"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.AssistantContent.Encoded": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Message.Assistant.Text_1"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.Assistant.Reasoning_1"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.Assistant.Tool_1"
+ }
+ ]
+ },
+ "Session.Message.Compaction": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Message.Compaction.Running"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.Compaction.Completed"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.Compaction.Failed"
+ }
+ ]
+ },
+ "Session.Message.Compaction.Completed": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "compaction"
+ ]
+ },
+ "id": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "completed"
+ ]
+ },
+ "reason": {
+ "type": "string",
+ "enum": [
+ "auto",
+ "manual"
+ ]
+ },
+ "model": {
+ "$ref": "#/components/schemas/Model.Ref"
+ },
+ "native": {
+ "$ref": "#/components/schemas/Session.Message.Native_4"
+ },
+ "summary": {
+ "type": "string"
+ },
+ "cost": {
+ "$ref": "#/components/schemas/Money.USD"
+ },
+ "tokens": {
+ "$ref": "#/components/schemas/TokenUsage.Info"
+ }
+ },
+ "required": [
+ "type",
+ "id",
+ "time",
+ "status",
+ "reason",
+ "summary"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.Compaction.Failed": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "compaction"
+ ]
+ },
+ "id": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "failed"
+ ]
+ },
+ "reason": {
+ "type": "string",
+ "enum": [
+ "auto",
+ "manual"
+ ]
+ },
+ "error": {
+ "$ref": "#/components/schemas/Session.StructuredError"
+ },
+ "cost": {
+ "$ref": "#/components/schemas/Money.USD"
+ },
+ "tokens": {
+ "$ref": "#/components/schemas/TokenUsage.Info"
+ }
+ },
+ "required": [
+ "type",
+ "id",
+ "time",
+ "status",
+ "reason",
+ "error"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.Compaction.Running": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "compaction"
+ ]
+ },
+ "id": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "running"
+ ]
+ },
+ "reason": {
+ "type": "string",
+ "enum": [
+ "auto",
+ "manual"
+ ]
+ },
+ "summary": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "id",
+ "time",
+ "status",
+ "reason",
+ "summary"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.ID": {
+ "type": "string",
+ "pattern": "^msg_"
+ },
+ "Session.Message.Idle": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "idle"
+ ]
+ },
+ "outcome": {
+ "type": "string",
+ "enum": [
+ "succeeded",
+ "failed",
+ "interrupted"
+ ]
+ },
+ "error": {
+ "$ref": "#/components/schemas/Session.StructuredError"
+ }
+ },
+ "required": [
+ "id",
+ "time",
+ "type",
+ "outcome"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.Info": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Message.AgentSelected"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.ModelSelected"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.LocationSwitched"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.User"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.Synthetic"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.System"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.Skill"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.Shell"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.Assistant"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.Compaction"
+ },
+ {
+ "$ref": "#/components/schemas/Session.Message.Idle"
+ }
+ ]
+ },
+ "Session.Message.LocationSwitched": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "location-switched"
+ ]
+ },
+ "projectID": {
+ "$ref": "#/components/schemas/Project.ID"
+ },
+ "subpath": {
+ "$ref": "#/components/schemas/RelativePath"
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "previous": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "projectID": {
+ "$ref": "#/components/schemas/Project.ID"
+ },
+ "subpath": {
+ "$ref": "#/components/schemas/RelativePath"
+ }
+ },
+ "required": [
+ "location"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "id",
+ "time",
+ "type",
+ "location"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.ModelSelected": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "model-switched"
+ ]
+ },
+ "model": {
+ "$ref": "#/components/schemas/Model.Ref"
+ },
+ "previous": {
+ "$ref": "#/components/schemas/Model.Ref"
+ }
+ },
+ "required": [
+ "id",
+ "time",
+ "type",
+ "model"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.Native": {
+ "type": "object"
+ },
+ "Session.Message.Native_1": {
+ "type": "object"
+ },
+ "Session.Message.Native_10": {
+ "type": "object"
+ },
+ "Session.Message.Native_11": {
+ "type": "object"
+ },
+ "Session.Message.Native_12": {
+ "type": "object"
+ },
+ "Session.Message.Native_13": {
+ "type": "object"
+ },
+ "Session.Message.Native_14": {
+ "type": "object"
+ },
+ "Session.Message.Native_15": {
+ "type": "object"
+ },
+ "Session.Message.Native_2": {
+ "type": "object"
+ },
+ "Session.Message.Native_3": {
+ "type": "object"
+ },
+ "Session.Message.Native_4": {
+ "type": "object"
+ },
+ "Session.Message.Native_5": {
+ "type": "object"
+ },
+ "Session.Message.Native_6": {
+ "type": "object"
+ },
+ "Session.Message.Native_7": {
+ "type": "object"
+ },
+ "Session.Message.Native_8": {
+ "type": "object"
+ },
+ "Session.Message.Native_9": {
+ "type": "object"
+ },
+ "Session.Message.Shell": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ },
+ "completed": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "shell"
+ ]
+ },
+ "shellID": {
+ "$ref": "#/components/schemas/Shell.ID"
+ },
+ "command": {
+ "type": "string"
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "running",
+ "exited",
+ "timeout",
+ "killed"
+ ]
+ },
+ "exit": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ "output": {
+ "type": "object",
+ "properties": {
+ "output": {
+ "type": "string"
+ },
+ "cursor": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "size": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "truncated": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "output",
+ "cursor",
+ "size",
+ "truncated"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "time",
+ "type",
+ "shellID",
+ "command",
+ "status"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.Skill": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "skill"
+ ]
+ },
+ "skill": {
+ "$ref": "#/components/schemas/Skill.ID"
+ },
+ "name": {
+ "$ref": "#/components/schemas/Skill.Name"
+ },
+ "text": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "time",
+ "type",
+ "skill",
+ "name",
+ "text"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.Synthetic": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ },
+ "text": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "synthetic"
+ ]
+ }
+ },
+ "required": [
+ "id",
+ "time",
+ "text",
+ "type"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.System": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "system"
+ ]
+ },
+ "text": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "time",
+ "type",
+ "text"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.ToolState.Completed": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "completed"
+ ]
+ },
+ "input": {
+ "type": "object"
+ },
+ "content": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/components/schemas/Tool.Content"
+ }
+ },
+ "metadata": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "status",
+ "input",
+ "content"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.ToolState.Completed_1": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "completed"
+ ]
+ },
+ "input": {
+ "type": "object"
+ },
+ "content": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/components/schemas/Tool.Content"
+ }
+ },
+ "metadata": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "status",
+ "input",
+ "content"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.ToolState.Error": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "error"
+ ]
+ },
+ "input": {
+ "type": "object"
+ },
+ "error": {
+ "$ref": "#/components/schemas/Session.StructuredError"
+ },
+ "content": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/components/schemas/Tool.Content"
+ }
+ },
+ "metadata": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "status",
+ "input",
+ "error"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.ToolState.Error_1": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "error"
+ ]
+ },
+ "input": {
+ "type": "object"
+ },
+ "error": {
+ "$ref": "#/components/schemas/Session.StructuredError_1"
+ },
+ "content": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/components/schemas/Tool.Content"
+ }
+ },
+ "metadata": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "status",
+ "input",
+ "error"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.ToolState.Running": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "running"
+ ]
+ },
+ "input": {
+ "type": "object"
+ },
+ "metadata": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "status",
+ "input",
+ "metadata"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.ToolState.Running_1": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "running"
+ ]
+ },
+ "input": {
+ "type": "object"
+ },
+ "metadata": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "status",
+ "input",
+ "metadata"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.ToolState.Streaming": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "streaming"
+ ]
+ },
+ "input": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "status",
+ "input"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Message.User": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "created": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "created"
+ ],
+ "additionalProperties": true
+ },
+ "text": {
+ "type": "string"
+ },
+ "files": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Prompt.FileAttachment"
+ }
+ },
+ "agents": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Prompt.AgentAttachment"
+ }
+ },
+ "skills": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Prompt.SkillAttachment"
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "user"
+ ]
+ }
+ },
+ "required": [
+ "id",
+ "time",
+ "text",
+ "type"
+ ],
+ "additionalProperties": true
+ },
+ "Session.Metadata": {
+ "type": "object"
+ },
+ "Session.Revert": {
+ "type": "object",
+ "properties": {
+ "messageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "partID": {
+ "type": "string"
+ },
+ "snapshot": {
+ "$ref": "#/components/schemas/Snapshot.ID"
+ },
+ "files": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/FileDiff.Info"
+ }
+ }
+ },
+ "required": [
+ "messageID"
+ ],
+ "additionalProperties": true
+ },
+ "Session.StructuredError": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ },
+ "status": {
+ "type": "integer",
+ "minimum": 100,
+ "maximum": 599
+ },
+ "response": {
+ "type": "object",
+ "properties": {
+ "body": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "body"
+ ],
+ "additionalProperties": true
+ },
+ "retryAfterMs": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "type",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "Session.StructuredError_1": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ },
+ "status": {
+ "type": "integer",
+ "minimum": 100,
+ "maximum": 599
+ },
+ "response": {
+ "type": "object",
+ "properties": {
+ "body": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "body"
+ ],
+ "additionalProperties": true
+ },
+ "retryAfterMs": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "type",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "SessionActive": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "running"
+ ]
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "additionalProperties": true
+ },
+ "SessionBusyErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "SessionBusyError"
+ ]
+ },
+ "sessionID": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "sessionID",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "SessionGenerateResponse": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "text": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "text"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "SessionInterruptResponse": {
+ "type": "object",
+ "properties": {
+ "interrupted": {
+ "type": "boolean",
+ "description": "Whether an active execution owned by this OpenCode process was interrupted."
+ }
+ },
+ "required": [
+ "interrupted"
+ ],
+ "additionalProperties": true
+ },
+ "SessionLogItem": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Session.Event.Durable"
+ },
+ {
+ "$ref": "#/components/schemas/EventLog.Synced"
+ }
+ ]
+ },
+ "SessionMessagesResponse": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Session.Message.Info"
+ }
+ },
+ "cursor": {
+ "type": "object",
+ "properties": {
+ "previous": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "next": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "data",
+ "cursor"
+ ],
+ "additionalProperties": true
+ },
+ "SessionNotFoundErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "SessionNotFoundError"
+ ]
+ },
+ "sessionID": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "sessionID",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "SessionStats.Activity": {
+ "type": "object",
+ "properties": {
+ "date": {
+ "type": "string"
+ },
+ "steps": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "date",
+ "steps"
+ ],
+ "additionalProperties": true
+ },
+ "SessionStats.Info": {
+ "type": "object",
+ "properties": {
+ "range": {
+ "type": "object",
+ "properties": {
+ "from": {
+ "type": "number"
+ },
+ "to": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "from",
+ "to"
+ ],
+ "additionalProperties": true
+ },
+ "sessions": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "subagents": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "prompts": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "steps": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "tokens": {
+ "$ref": "#/components/schemas/TokenUsage.Info"
+ },
+ "cost": {
+ "$ref": "#/components/schemas/Money.USD"
+ },
+ "tools": {
+ "$ref": "#/components/schemas/SessionStats.Tools"
+ },
+ "activeDays": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "streak": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "activity": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/SessionStats.Activity"
+ }
+ },
+ "models": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/SessionStats.ModelUsage"
+ }
+ }
+ },
+ "required": [
+ "range",
+ "sessions",
+ "subagents",
+ "prompts",
+ "steps",
+ "tokens",
+ "cost",
+ "tools",
+ "activeDays",
+ "streak",
+ "activity",
+ "models"
+ ],
+ "additionalProperties": true
+ },
+ "SessionStats.ModelUsage": {
+ "type": "object",
+ "properties": {
+ "model": {
+ "$ref": "#/components/schemas/Model.Ref"
+ },
+ "steps": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "tokens": {
+ "$ref": "#/components/schemas/TokenUsage.Info"
+ },
+ "cost": {
+ "$ref": "#/components/schemas/Money.USD"
+ }
+ },
+ "required": [
+ "model",
+ "steps",
+ "tokens",
+ "cost"
+ ],
+ "additionalProperties": true
+ },
+ "SessionStats.ToolTotals": {
+ "type": "object",
+ "properties": {
+ "calls": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "succeeded": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "failed": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "unfinished": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "calls",
+ "succeeded",
+ "failed",
+ "unfinished"
+ ],
+ "additionalProperties": true
+ },
+ "SessionStats.ToolUsage": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "calls": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "succeeded": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "failed": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "unfinished": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "durationP50": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "name",
+ "calls",
+ "succeeded",
+ "failed",
+ "unfinished"
+ ],
+ "additionalProperties": true
+ },
+ "SessionStats.Tools": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "mode": {
+ "type": "string",
+ "enum": [
+ "none"
+ ]
+ }
+ },
+ "required": [
+ "mode"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "mode": {
+ "type": "string",
+ "enum": [
+ "summary"
+ ]
+ },
+ "totals": {
+ "$ref": "#/components/schemas/SessionStats.ToolTotals"
+ }
+ },
+ "required": [
+ "mode",
+ "totals"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "mode": {
+ "type": "string",
+ "enum": [
+ "detail"
+ ]
+ },
+ "totals": {
+ "$ref": "#/components/schemas/SessionStats.ToolTotals"
+ },
+ "usage": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/SessionStats.ToolUsage"
+ }
+ }
+ },
+ "required": [
+ "mode",
+ "totals",
+ "usage"
+ ],
+ "additionalProperties": true
+ }
+ ]
+ },
+ "SessionStatus": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "idle"
+ ]
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "retry"
+ ]
+ },
+ "attempt": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "message": {
+ "type": "string"
+ },
+ "action": {
+ "type": "object",
+ "properties": {
+ "reason": {
+ "type": "string"
+ },
+ "provider": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ },
+ "label": {
+ "type": "string"
+ },
+ "link": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "reason",
+ "provider",
+ "title",
+ "message",
+ "label"
+ ],
+ "additionalProperties": true
+ },
+ "next": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "type",
+ "attempt",
+ "message",
+ "next"
+ ],
+ "additionalProperties": true
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "busy"
+ ]
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "additionalProperties": true
+ }
+ ]
+ },
+ "SessionStatusUpdated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.status"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "status": {
+ "$ref": "#/components/schemas/SessionStatus"
+ }
+ },
+ "required": [
+ "sessionID",
+ "status"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "SessionTransfer.Data": {
+ "type": "object",
+ "properties": {
+ "info": {
+ "$ref": "#/components/schemas/Session.Info"
+ },
+ "messages": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Session.Message.Info"
+ }
+ }
+ },
+ "required": [
+ "info",
+ "messages"
+ ],
+ "additionalProperties": true
+ },
+ "SessionsCursor": {
+ "type": "string",
+ "description": "Opaque pagination cursor returned as cursor.previous or cursor.next in the previous response."
+ },
+ "SessionsResponse": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Session.Info"
+ }
+ },
+ "cursor": {
+ "type": "object",
+ "properties": {
+ "previous": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionsCursor"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "next": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/SessionsCursor"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "data",
+ "cursor"
+ ],
+ "additionalProperties": true
+ },
+ "Shell.ID": {
+ "type": "string",
+ "pattern": "^sh_"
+ },
+ "Shell.Info": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Shell.ID"
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "running",
+ "exited",
+ "timeout",
+ "killed"
+ ]
+ },
+ "command": {
+ "type": "string"
+ },
+ "cwd": {
+ "type": "string"
+ },
+ "shell": {
+ "type": "string"
+ },
+ "file": {
+ "type": "string"
+ },
+ "pid": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "exit": {
+ "type": "number"
+ },
+ "signal": {
+ "type": "string"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "started": {
+ "type": "number",
+ "description": "Start time in milliseconds since the Unix epoch"
+ },
+ "completed": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "started"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "status",
+ "command",
+ "cwd",
+ "shell",
+ "file",
+ "metadata",
+ "time"
+ ],
+ "additionalProperties": true
+ },
+ "ShellNotFoundErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "ShellNotFoundError"
+ ]
+ },
+ "id": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "id",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "Skill.ID": {
+ "type": "string"
+ },
+ "Skill.Info": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Skill.ID"
+ },
+ "name": {
+ "$ref": "#/components/schemas/Skill.Name"
+ },
+ "description": {
+ "type": "string"
+ },
+ "autoinvoke": {
+ "type": "boolean"
+ },
+ "path": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ },
+ "content": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "path",
+ "content"
+ ],
+ "additionalProperties": true
+ },
+ "Skill.Name": {
+ "type": "string"
+ },
+ "SkillNotFoundErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "SkillNotFoundError"
+ ]
+ },
+ "skill": {
+ "$ref": "#/components/schemas/Skill.ID"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "skill",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "Snapshot.ID": {
+ "type": "string"
+ },
+ "TokenUsage.Info": {
+ "type": "object",
+ "properties": {
+ "input": {
+ "type": "number"
+ },
+ "output": {
+ "type": "number"
+ },
+ "reasoning": {
+ "type": "number"
+ },
+ "cache": {
+ "type": "object",
+ "properties": {
+ "read": {
+ "type": "number"
+ },
+ "write": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "read",
+ "write"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "input",
+ "output",
+ "reasoning",
+ "cache"
+ ],
+ "additionalProperties": true
+ },
+ "Tool.Content": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Tool.TextContent"
+ },
+ {
+ "$ref": "#/components/schemas/Tool.FileContent"
+ }
+ ]
+ },
+ "Tool.FileContent": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "file"
+ ]
+ },
+ "uri": {
+ "type": "string"
+ },
+ "mime": {
+ "type": "string"
+ },
+ "name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "type",
+ "uri",
+ "mime"
+ ],
+ "additionalProperties": true
+ },
+ "Tool.TextContent": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "text"
+ ]
+ },
+ "text": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "text"
+ ],
+ "additionalProperties": true
+ },
+ "UnauthorizedErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "UnauthorizedError"
+ ]
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "UnknownErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "UnknownError"
+ ]
+ },
+ "message": {
+ "type": "string"
+ },
+ "ref": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "_tag",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "V2Event": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/location.shutdown"
+ },
+ {
+ "$ref": "#/components/schemas/models-dev.refreshed"
+ },
+ {
+ "$ref": "#/components/schemas/credential.updated"
+ },
+ {
+ "$ref": "#/components/schemas/credential.switched"
+ },
+ {
+ "$ref": "#/components/schemas/integration.updated"
+ },
+ {
+ "$ref": "#/components/schemas/provider.updated"
+ },
+ {
+ "$ref": "#/components/schemas/model.updated"
+ },
+ {
+ "$ref": "#/components/schemas/agent.updated"
+ },
+ {
+ "$ref": "#/components/schemas/session.created"
+ },
+ {
+ "$ref": "#/components/schemas/session.agent.selected"
+ },
+ {
+ "$ref": "#/components/schemas/session.model.selected"
+ },
+ {
+ "$ref": "#/components/schemas/session.moved"
+ },
+ {
+ "$ref": "#/components/schemas/session.renamed"
+ },
+ {
+ "$ref": "#/components/schemas/session.metadata.updated"
+ },
+ {
+ "$ref": "#/components/schemas/session.permissions"
+ },
+ {
+ "$ref": "#/components/schemas/session.viewed"
+ },
+ {
+ "$ref": "#/components/schemas/session.archived"
+ },
+ {
+ "$ref": "#/components/schemas/session.unarchived"
+ },
+ {
+ "$ref": "#/components/schemas/session.usage.updated"
+ },
+ {
+ "$ref": "#/components/schemas/session.deleted"
+ },
+ {
+ "$ref": "#/components/schemas/session.forked"
+ },
+ {
+ "$ref": "#/components/schemas/session.inbox.delivered"
+ },
+ {
+ "$ref": "#/components/schemas/session.inbox.enqueued"
+ },
+ {
+ "$ref": "#/components/schemas/session.inbox.cancelled"
+ },
+ {
+ "$ref": "#/components/schemas/session.inbox.delivery.changed"
+ },
+ {
+ "$ref": "#/components/schemas/session.execution.started"
+ },
+ {
+ "$ref": "#/components/schemas/session.execution.succeeded"
+ },
+ {
+ "$ref": "#/components/schemas/session.execution.failed"
+ },
+ {
+ "$ref": "#/components/schemas/session.execution.interrupted"
+ },
+ {
+ "$ref": "#/components/schemas/session.instructions.updated"
+ },
+ {
+ "$ref": "#/components/schemas/session.synthetic"
+ },
+ {
+ "$ref": "#/components/schemas/session.skill.activated"
+ },
+ {
+ "$ref": "#/components/schemas/session.shell.started"
+ },
+ {
+ "$ref": "#/components/schemas/session.shell.ended"
+ },
+ {
+ "$ref": "#/components/schemas/session.step.started"
+ },
+ {
+ "$ref": "#/components/schemas/session.step.streamed"
+ },
+ {
+ "$ref": "#/components/schemas/session.step.ended"
+ },
+ {
+ "$ref": "#/components/schemas/session.step.failed"
+ },
+ {
+ "$ref": "#/components/schemas/session.text.started"
+ },
+ {
+ "$ref": "#/components/schemas/session.text.delta"
+ },
+ {
+ "$ref": "#/components/schemas/session.text.ended"
+ },
+ {
+ "$ref": "#/components/schemas/session.reasoning.started"
+ },
+ {
+ "$ref": "#/components/schemas/session.reasoning.delta"
+ },
+ {
+ "$ref": "#/components/schemas/session.reasoning.ended"
+ },
+ {
+ "$ref": "#/components/schemas/session.tool.input.started"
+ },
+ {
+ "$ref": "#/components/schemas/session.tool.input.delta"
+ },
+ {
+ "$ref": "#/components/schemas/session.tool.input.ended"
+ },
+ {
+ "$ref": "#/components/schemas/session.tool.called"
+ },
+ {
+ "$ref": "#/components/schemas/session.tool.progress"
+ },
+ {
+ "$ref": "#/components/schemas/session.tool.success"
+ },
+ {
+ "$ref": "#/components/schemas/session.tool.failed"
+ },
+ {
+ "$ref": "#/components/schemas/session.retry.scheduled"
+ },
+ {
+ "$ref": "#/components/schemas/session.compaction.started"
+ },
+ {
+ "$ref": "#/components/schemas/session.compaction.delta"
+ },
+ {
+ "$ref": "#/components/schemas/session.compaction.ended"
+ },
+ {
+ "$ref": "#/components/schemas/session.compaction.failed"
+ },
+ {
+ "$ref": "#/components/schemas/session.revert.staged"
+ },
+ {
+ "$ref": "#/components/schemas/session.revert.cleared"
+ },
+ {
+ "$ref": "#/components/schemas/session.revert.committed"
+ },
+ {
+ "$ref": "#/components/schemas/filesystem.changed"
+ },
+ {
+ "$ref": "#/components/schemas/reference.updated"
+ },
+ {
+ "$ref": "#/components/schemas/permission.asked"
+ },
+ {
+ "$ref": "#/components/schemas/permission.replied"
+ },
+ {
+ "$ref": "#/components/schemas/plugin.updated"
+ },
+ {
+ "$ref": "#/components/schemas/project.updated"
+ },
+ {
+ "$ref": "#/components/schemas/worktree.updated"
+ },
+ {
+ "$ref": "#/components/schemas/worktree.resolved"
+ },
+ {
+ "$ref": "#/components/schemas/command.updated"
+ },
+ {
+ "$ref": "#/components/schemas/config.updated"
+ },
+ {
+ "$ref": "#/components/schemas/skill.updated"
+ },
+ {
+ "$ref": "#/components/schemas/pty.created"
+ },
+ {
+ "$ref": "#/components/schemas/pty.updated"
+ },
+ {
+ "$ref": "#/components/schemas/pty.exited"
+ },
+ {
+ "$ref": "#/components/schemas/pty.deleted"
+ },
+ {
+ "$ref": "#/components/schemas/persistent-pty.added"
+ },
+ {
+ "$ref": "#/components/schemas/persistent-pty.removed"
+ },
+ {
+ "$ref": "#/components/schemas/shell.created"
+ },
+ {
+ "$ref": "#/components/schemas/shell.exited"
+ },
+ {
+ "$ref": "#/components/schemas/shell.deleted"
+ },
+ {
+ "$ref": "#/components/schemas/form.created"
+ },
+ {
+ "$ref": "#/components/schemas/form.replied"
+ },
+ {
+ "$ref": "#/components/schemas/form.cancelled"
+ },
+ {
+ "$ref": "#/components/schemas/websearch.updated"
+ },
+ {
+ "$ref": "#/components/schemas/SessionStatusUpdated"
+ },
+ {
+ "$ref": "#/components/schemas/session.idle"
+ },
+ {
+ "$ref": "#/components/schemas/tui.prompt.append"
+ },
+ {
+ "$ref": "#/components/schemas/tui.command.execute"
+ },
+ {
+ "$ref": "#/components/schemas/tui.toast.show"
+ },
+ {
+ "$ref": "#/components/schemas/tui.session.select"
+ },
+ {
+ "$ref": "#/components/schemas/installation.updated"
+ },
+ {
+ "$ref": "#/components/schemas/installation.update-available"
+ },
+ {
+ "$ref": "#/components/schemas/vcs.branch.updated"
+ },
+ {
+ "$ref": "#/components/schemas/mcp.status.changed"
+ },
+ {
+ "$ref": "#/components/schemas/mcp.resources.changed"
+ },
+ {
+ "$ref": "#/components/schemas/V2Event.rpc"
+ },
+ {
+ "$ref": "#/components/schemas/V2Event.server.connected"
+ }
+ ]
+ },
+ "V2Event.rpc": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "type": {
+ "type": "string",
+ "pattern": "^rpc\\.[\\s\\S]*?$"
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ "data": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "location",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "V2Event.server.connected": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "metadata": {
+ "anyOf": [
+ {
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "location": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Location.PublicRef"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "server.connected"
+ ]
+ },
+ "data": {
+ "not": {
+ "type": "null"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "Vcs.Base": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "ref": {
+ "type": "string"
+ },
+ "source": {
+ "type": "string",
+ "enum": [
+ "reflog",
+ "default"
+ ]
+ }
+ },
+ "required": [
+ "name",
+ "ref",
+ "source"
+ ],
+ "additionalProperties": true
+ },
+ "Vcs.Branch": {
+ "type": "object",
+ "properties": {
+ "current": {
+ "type": "string"
+ },
+ "default": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": true
+ },
+ "Vcs.BranchList": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "Vcs.FileStatus": {
+ "type": "object",
+ "properties": {
+ "file": {
+ "type": "string"
+ },
+ "additions": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "deletions": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "added",
+ "deleted",
+ "modified"
+ ]
+ }
+ },
+ "required": [
+ "file",
+ "additions",
+ "deletions",
+ "status"
+ ],
+ "additionalProperties": true
+ },
+ "Vcs.Info": {
+ "type": "object",
+ "properties": {
+ "provider": {
+ "type": "string"
+ },
+ "branch": {
+ "$ref": "#/components/schemas/Vcs.Branch"
+ }
+ },
+ "required": [
+ "branch"
+ ],
+ "additionalProperties": true
+ },
+ "Vcs.Mode": {
+ "type": "string",
+ "enum": [
+ "working",
+ "branch",
+ "committed"
+ ]
+ },
+ "VcsInitNotSupportedErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "VcsInitNotSupportedError"
+ ]
+ },
+ "providerID": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "_tag",
+ "providerID",
+ "message"
+ ],
+ "additionalProperties": true
+ },
+ "VoiceSessionResponse": {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "transport": {
+ "$ref": "#/components/schemas/VoiceTransport"
+ }
+ },
+ "required": [
+ "transport"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "VoiceTransport": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/VoiceWebRTCTransport"
+ },
+ {
+ "$ref": "#/components/schemas/VoiceWebSocketTransport"
+ }
+ ]
+ },
+ "VoiceTransportInput": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/VoiceWebRTCTransportInput"
+ },
+ {
+ "$ref": "#/components/schemas/VoiceWebSocketTransportInput"
+ }
+ ]
+ },
+ "VoiceWebRTCTransport": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "webrtc"
+ ]
+ },
+ "sdp": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "sdp"
+ ],
+ "additionalProperties": true
+ },
+ "VoiceWebRTCTransportInput": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "webrtc"
+ ]
+ },
+ "sdp": {
+ "type": "string"
+ },
+ "dcid": {
+ "anyOf": [
+ {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "type",
+ "sdp"
+ ],
+ "additionalProperties": true
+ },
+ "VoiceWebSocketTransport": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "websocket"
+ ]
+ },
+ "url": {
+ "type": "string"
+ },
+ "token": {
+ "type": "string"
+ },
+ "expiresAt": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "Infinity",
+ "-Infinity",
+ "NaN"
+ ]
+ }
+ ]
+ }
+ },
+ "required": [
+ "type",
+ "url",
+ "token",
+ "expiresAt"
+ ],
+ "additionalProperties": true
+ },
+ "VoiceWebSocketTransportInput": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "websocket"
+ ]
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "additionalProperties": true
+ },
+ "WebSearch.ID": {
+ "type": "string"
+ },
+ "WebSearch.Provider": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/WebSearch.ID"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "name"
+ ],
+ "additionalProperties": true
+ },
+ "WebSearch.ResponseEncoded": {
+ "type": "object",
+ "properties": {
+ "providerID": {
+ "$ref": "#/components/schemas/WebSearch.ID"
+ },
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/WebSearch.Result"
+ }
+ }
+ },
+ "required": [
+ "providerID",
+ "results"
+ ],
+ "additionalProperties": true
+ },
+ "WebSearch.Result": {
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "content": {
+ "type": "string"
+ },
+ "time": {
+ "type": "object",
+ "properties": {
+ "published": {
+ "type": "number"
+ }
+ },
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "url",
+ "time"
+ ],
+ "additionalProperties": true
+ },
+ "Workspace.ID": {
+ "type": "string",
+ "pattern": "^wrk"
+ },
+ "Worktree.Directory": {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ },
+ "strategy": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "directory"
+ ],
+ "additionalProperties": true
+ },
+ "Worktree.Info": {
+ "type": "object",
+ "properties": {
+ "directory": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ }
+ },
+ "required": [
+ "directory"
+ ],
+ "additionalProperties": true
+ },
+ "Worktree.List": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Worktree.Directory"
+ }
+ },
+ "WorktreeErrorEncoded": {
+ "type": "object",
+ "properties": {
+ "_tag": {
+ "type": "string",
+ "enum": [
+ "WorktreeError"
+ ]
+ },
+ "name": {
+ "type": "string",
+ "enum": [
+ "WorktreeError"
+ ]
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "message": {
+ "type": "string"
+ },
+ "forceRequired": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "message"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "_tag",
+ "name",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "agent.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "agent.updated"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "not": {
+ "type": "null"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "command.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "command.updated"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "not": {
+ "type": "null"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "config.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "config.updated"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "not": {
+ "type": "null"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "credential.switched": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "credential.switched"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "integrationID": {
+ "$ref": "#/components/schemas/Integration.ID"
+ },
+ "credentialID": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Credential.ID"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "integrationID",
+ "credentialID"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "credential.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "credential.updated"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "not": {
+ "type": "null"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "filesystem.changed": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "filesystem.changed"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "file": {
+ "type": "string"
+ },
+ "event": {
+ "type": "string",
+ "enum": [
+ "add",
+ "change",
+ "unlink"
+ ]
+ }
+ },
+ "required": [
+ "file",
+ "event"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "form.cancelled": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "form.cancelled"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Form.ID"
+ },
+ "sessionID": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "sessionID"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "form.created": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "form.created"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "form": {
+ "$ref": "#/components/schemas/Form.Info"
+ }
+ },
+ "required": [
+ "form"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "form.replied": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "form.replied"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Form.ID"
+ },
+ "sessionID": {
+ "type": "string"
+ },
+ "answer": {
+ "$ref": "#/components/schemas/Form.Answer"
+ }
+ },
+ "required": [
+ "id",
+ "sessionID",
+ "answer"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "installation.update-available": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "installation.update-available"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "version": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "version"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "installation.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "installation.updated"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "version": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "version"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "integration.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "integration.updated"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "not": {
+ "type": "null"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "location.shutdown": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "location.shutdown"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "not": {
+ "type": "null"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "mcp.resources.changed": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "mcp.resources.changed"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "server": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "server"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "mcp.status.changed": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "mcp.status.changed"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "server": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "server"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "model.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "model.updated"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "not": {
+ "type": "null"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "models-dev.refreshed": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "models-dev.refreshed"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "not": {
+ "type": "null"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "permission.asked": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "permission.asked"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Permission.ID"
+ },
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "action": {
+ "type": "string"
+ },
+ "resources": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "save": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "source": {
+ "$ref": "#/components/schemas/Permission.Source"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "sessionID",
+ "action",
+ "resources"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "permission.replied": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "permission.replied"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "requestID": {
+ "$ref": "#/components/schemas/Permission.ID"
+ },
+ "reply": {
+ "$ref": "#/components/schemas/Permission.Reply"
+ }
+ },
+ "required": [
+ "sessionID",
+ "requestID",
+ "reply"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "persistent-pty.added": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "persistent-pty.added"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "terminal": {
+ "$ref": "#/components/schemas/PersistentPty.Info"
+ }
+ },
+ "required": [
+ "sessionID",
+ "terminal"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "persistent-pty.removed": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "persistent-pty.removed"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "ptyID": {
+ "$ref": "#/components/schemas/Pty.ID"
+ }
+ },
+ "required": [
+ "sessionID",
+ "ptyID"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "plugin.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "plugin.updated"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "not": {
+ "type": "null"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "project.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "project.updated"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Project.ID"
+ },
+ "canonical": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ },
+ "vcs": {
+ "$ref": "#/components/schemas/Project.Vcs"
+ },
+ "name": {
+ "type": "string"
+ },
+ "icon": {
+ "$ref": "#/components/schemas/Project.Icon"
+ },
+ "commands": {
+ "$ref": "#/components/schemas/Project.Commands"
+ },
+ "time": {
+ "$ref": "#/components/schemas/Project.Time"
+ },
+ "sandboxes": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "canonical",
+ "time",
+ "sandboxes"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "provider.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "provider.updated"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "not": {
+ "type": "null"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "pty.created": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "pty.created"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "info": {
+ "$ref": "#/components/schemas/Pty"
+ }
+ },
+ "required": [
+ "info"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "pty.deleted": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "pty.deleted"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Pty.ID"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "pty.exited": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "pty.exited"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Pty.ID"
+ },
+ "exitCode": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "id",
+ "exitCode"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "pty.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "pty.updated"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "info": {
+ "$ref": "#/components/schemas/Pty"
+ }
+ },
+ "required": [
+ "info"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "reference.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "reference.updated"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "not": {
+ "type": "null"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.agent.selected": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.agent.selected"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "agent": {
+ "$ref": "#/components/schemas/Agent.ID"
+ },
+ "previous": {
+ "$ref": "#/components/schemas/Agent.ID"
+ }
+ },
+ "required": [
+ "sessionID",
+ "agent"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.archived": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.archived"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ "required": [
+ "sessionID"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.compaction.delta": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.compaction.delta"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "text": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "sessionID",
+ "text"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.compaction.ended": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.compaction.ended"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "reason": {
+ "type": "string",
+ "enum": [
+ "auto",
+ "manual"
+ ]
+ },
+ "model": {
+ "$ref": "#/components/schemas/Model.Ref"
+ },
+ "native": {
+ "$ref": "#/components/schemas/Session.Message.Native_4"
+ },
+ "text": {
+ "type": "string"
+ },
+ "cost": {
+ "$ref": "#/components/schemas/Money.USD"
+ },
+ "tokens": {
+ "$ref": "#/components/schemas/TokenUsage.Info"
+ }
+ },
+ "required": [
+ "sessionID",
+ "reason",
+ "text"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.compaction.failed": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.compaction.failed"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "reason": {
+ "type": "string",
+ "enum": [
+ "auto",
+ "manual"
+ ]
+ },
+ "error": {
+ "$ref": "#/components/schemas/Session.StructuredError"
+ },
+ "inputID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "cost": {
+ "$ref": "#/components/schemas/Money.USD"
+ },
+ "tokens": {
+ "$ref": "#/components/schemas/TokenUsage.Info"
+ }
+ },
+ "required": [
+ "sessionID",
+ "reason",
+ "error"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.compaction.started": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.compaction.started"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "reason": {
+ "type": "string",
+ "enum": [
+ "auto",
+ "manual"
+ ]
+ },
+ "inputID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ }
+ },
+ "required": [
+ "sessionID",
+ "reason"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.created": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.created"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "projectID": {
+ "$ref": "#/components/schemas/Project.ID"
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "subpath": {
+ "$ref": "#/components/schemas/RelativePath"
+ },
+ "parentID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "slug": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "agent": {
+ "$ref": "#/components/schemas/Agent.ID"
+ },
+ "model": {
+ "$ref": "#/components/schemas/Model.Ref"
+ },
+ "metadata": {
+ "$ref": "#/components/schemas/Session.Metadata"
+ },
+ "permissions": {
+ "$ref": "#/components/schemas/Permission.Ruleset"
+ },
+ "version": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "sessionID",
+ "projectID",
+ "location",
+ "slug",
+ "version"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.deleted": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.deleted"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded_10"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ "required": [
+ "sessionID"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.execution.failed": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.execution.failed"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "error": {
+ "$ref": "#/components/schemas/Session.StructuredError"
+ }
+ },
+ "required": [
+ "sessionID",
+ "error"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.execution.interrupted": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.execution.interrupted"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "reason": {
+ "type": "string",
+ "enum": [
+ "user",
+ "shutdown",
+ "superseded",
+ "inactivity"
+ ]
+ }
+ },
+ "required": [
+ "sessionID",
+ "reason"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.execution.started": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.execution.started"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ "required": [
+ "sessionID"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.execution.succeeded": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.execution.succeeded"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ "required": [
+ "sessionID"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.forked": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.forked"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded_10"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "parentID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "boundary": {
+ "$ref": "#/components/schemas/Session.ForkBoundary"
+ },
+ "instructions": {
+ "type": "object",
+ "patternProperties": {
+ "^[a-z0-9][a-z0-9._-]*\\/[a-z0-9][a-z0-9._/-]*$": {
+ "$ref": "#/components/schemas/Instruction.Hash"
+ }
+ },
+ "additionalProperties": true
+ },
+ "instructionEntries": {
+ "$ref": "#/components/schemas/InstructionEntry.Snapshot"
+ }
+ },
+ "required": [
+ "sessionID",
+ "parentID",
+ "boundary"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.idle": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.idle"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ "required": [
+ "sessionID"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.inbox.cancelled": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.inbox.cancelled"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "inboxID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ }
+ },
+ "required": [
+ "sessionID",
+ "inboxID"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.inbox.delivered": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.inbox.delivered"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "inboxID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ }
+ },
+ "required": [
+ "sessionID",
+ "inboxID"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.inbox.delivery.changed": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.inbox.delivery.changed"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "inboxID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "delivery": {
+ "$ref": "#/components/schemas/Session.Inbox.Delivery"
+ }
+ },
+ "required": [
+ "sessionID",
+ "inboxID",
+ "delivery"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.inbox.enqueued": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.inbox.enqueued"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "inboxID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "item": {
+ "$ref": "#/components/schemas/Session.Inbox.Item"
+ }
+ },
+ "required": [
+ "sessionID",
+ "inboxID",
+ "item"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.instructions.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.instructions.updated"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded_10"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "delta": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/Instruction.Hash"
+ },
+ {
+ "type": "string",
+ "enum": [
+ "removed"
+ ]
+ }
+ ]
+ }
+ },
+ "text": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "sessionID",
+ "delta"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.message.content.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.message.content.updated"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "messageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "content": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Session.Message.AssistantContent.Encoded"
+ }
+ }
+ },
+ "required": [
+ "sessionID",
+ "messageID",
+ "content"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.metadata.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.metadata.updated"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "metadata": {
+ "$ref": "#/components/schemas/Session.Metadata"
+ }
+ },
+ "required": [
+ "sessionID",
+ "metadata"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.model.selected": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.model.selected"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "model": {
+ "$ref": "#/components/schemas/Model.Ref"
+ },
+ "previous": {
+ "$ref": "#/components/schemas/Model.Ref"
+ }
+ },
+ "required": [
+ "sessionID",
+ "model"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.moved": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.moved"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "projectID": {
+ "$ref": "#/components/schemas/Project.ID"
+ },
+ "subpath": {
+ "$ref": "#/components/schemas/RelativePath"
+ }
+ },
+ "required": [
+ "sessionID",
+ "location",
+ "projectID"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.permissions": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.permissions"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "permissions": {
+ "$ref": "#/components/schemas/Permission.Ruleset"
+ }
+ },
+ "required": [
+ "sessionID",
+ "permissions"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.reasoning.delta": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.reasoning.delta"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "assistantMessageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "ordinal": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "delta": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "sessionID",
+ "assistantMessageID",
+ "ordinal",
+ "delta"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.reasoning.ended": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.reasoning.ended"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "assistantMessageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "ordinal": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "text": {
+ "type": "string"
+ },
+ "native": {
+ "$ref": "#/components/schemas/Session.Message.Native_9"
+ }
+ },
+ "required": [
+ "sessionID",
+ "assistantMessageID",
+ "ordinal",
+ "text"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.reasoning.started": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.reasoning.started"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "assistantMessageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "ordinal": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "native": {
+ "$ref": "#/components/schemas/Session.Message.Native_8"
+ }
+ },
+ "required": [
+ "sessionID",
+ "assistantMessageID",
+ "ordinal"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.renamed": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.renamed"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "title": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "sessionID",
+ "title"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.retry.scheduled": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.retry.scheduled"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "assistantMessageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "attempt": {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ "at": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "error": {
+ "$ref": "#/components/schemas/Session.StructuredError"
+ }
+ },
+ "required": [
+ "sessionID",
+ "assistantMessageID",
+ "attempt",
+ "at",
+ "error"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.revert.cleared": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.revert.cleared"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ "required": [
+ "sessionID"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.revert.committed": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.revert.committed"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "to": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ }
+ },
+ "required": [
+ "sessionID",
+ "to"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.revert.staged": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.revert.staged"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "revert": {
+ "$ref": "#/components/schemas/Session.Revert"
+ }
+ },
+ "required": [
+ "sessionID",
+ "revert"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.shell.ended": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.shell.ended"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "shell": {
+ "$ref": "#/components/schemas/Shell.Info"
+ },
+ "output": {
+ "type": "object",
+ "properties": {
+ "output": {
+ "type": "string"
+ },
+ "cursor": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "size": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "truncated": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "output",
+ "cursor",
+ "size",
+ "truncated"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "sessionID",
+ "shell",
+ "output"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.shell.started": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.shell.started"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "shell": {
+ "$ref": "#/components/schemas/Shell.Info"
+ }
+ },
+ "required": [
+ "sessionID",
+ "shell"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.skill.activated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.skill.activated"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "id": {
+ "$ref": "#/components/schemas/Skill.ID"
+ },
+ "name": {
+ "$ref": "#/components/schemas/Skill.Name"
+ },
+ "text": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "sessionID",
+ "id",
+ "name",
+ "text"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.step.ended": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.step.ended"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "assistantMessageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "finish": {
+ "type": "string",
+ "enum": [
+ "stop",
+ "length",
+ "tool-calls",
+ "content-filter",
+ "error",
+ "unknown"
+ ]
+ },
+ "rawFinish": {
+ "type": "string"
+ },
+ "native": {
+ "$ref": "#/components/schemas/Session.Message.Native_5"
+ },
+ "cost": {
+ "$ref": "#/components/schemas/Money.USD"
+ },
+ "tokens": {
+ "$ref": "#/components/schemas/TokenUsage.Info"
+ },
+ "snapshot": {
+ "$ref": "#/components/schemas/Snapshot.ID"
+ },
+ "files": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RelativePath"
+ }
+ }
+ },
+ "required": [
+ "sessionID",
+ "assistantMessageID",
+ "finish",
+ "cost",
+ "tokens"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.step.failed": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.step.failed"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "assistantMessageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "error": {
+ "$ref": "#/components/schemas/Session.StructuredError"
+ },
+ "finish": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "content-filter"
+ ]
+ }
+ ]
+ },
+ "rawFinish": {
+ "type": "string"
+ },
+ "native": {
+ "$ref": "#/components/schemas/Session.Message.Native_6"
+ },
+ "cost": {
+ "$ref": "#/components/schemas/Money.USD"
+ },
+ "tokens": {
+ "$ref": "#/components/schemas/TokenUsage.Info"
+ },
+ "snapshot": {
+ "$ref": "#/components/schemas/Snapshot.ID"
+ },
+ "files": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/RelativePath"
+ }
+ }
+ },
+ "required": [
+ "sessionID",
+ "assistantMessageID",
+ "error"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.step.started": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.step.started"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "assistantMessageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "agent": {
+ "$ref": "#/components/schemas/Agent.ID"
+ },
+ "model": {
+ "$ref": "#/components/schemas/Model.Ref"
+ },
+ "snapshot": {
+ "$ref": "#/components/schemas/Snapshot.ID"
+ },
+ "started": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "sessionID",
+ "assistantMessageID",
+ "agent",
+ "model",
+ "started"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.step.streamed": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.step.streamed"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "assistantMessageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ }
+ },
+ "required": [
+ "sessionID",
+ "assistantMessageID"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.synthetic": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.synthetic"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "text": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "metadata": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "sessionID",
+ "text"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.text.delta": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.text.delta"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "assistantMessageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "ordinal": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "delta": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "sessionID",
+ "assistantMessageID",
+ "ordinal",
+ "delta"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.text.ended": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.text.ended"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "assistantMessageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "ordinal": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "text": {
+ "type": "string"
+ },
+ "native": {
+ "$ref": "#/components/schemas/Session.Message.Native_7"
+ }
+ },
+ "required": [
+ "sessionID",
+ "assistantMessageID",
+ "ordinal",
+ "text"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.text.started": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.text.started"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "assistantMessageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "ordinal": {
+ "type": "integer",
+ "minimum": 0
+ }
+ },
+ "required": [
+ "sessionID",
+ "assistantMessageID",
+ "ordinal"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.tool.called": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.tool.called"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "assistantMessageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "id": {
+ "type": "string"
+ },
+ "input": {
+ "type": "object"
+ },
+ "executed": {
+ "type": "boolean"
+ },
+ "native": {
+ "$ref": "#/components/schemas/Session.Message.Native_10"
+ }
+ },
+ "required": [
+ "sessionID",
+ "assistantMessageID",
+ "id",
+ "input",
+ "executed"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.tool.failed": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.tool.failed"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded_10"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "assistantMessageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "id": {
+ "type": "string"
+ },
+ "error": {
+ "$ref": "#/components/schemas/Session.StructuredError"
+ },
+ "content": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/components/schemas/Tool.Content"
+ }
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "executed": {
+ "type": "boolean"
+ },
+ "native": {
+ "$ref": "#/components/schemas/Session.Message.Native_12"
+ }
+ },
+ "required": [
+ "sessionID",
+ "assistantMessageID",
+ "id",
+ "error",
+ "executed"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.tool.input.delta": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.tool.input.delta"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "assistantMessageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "id": {
+ "type": "string"
+ },
+ "delta": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "sessionID",
+ "assistantMessageID",
+ "id",
+ "delta"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.tool.input.ended": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.tool.input.ended"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "assistantMessageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "id": {
+ "type": "string"
+ },
+ "text": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "sessionID",
+ "assistantMessageID",
+ "id",
+ "text"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.tool.input.started": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.tool.input.started"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "assistantMessageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "id": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "sessionID",
+ "assistantMessageID",
+ "id",
+ "name"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.tool.progress": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.tool.progress"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "assistantMessageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "id": {
+ "type": "string"
+ },
+ "metadata": {
+ "type": "object"
+ }
+ },
+ "required": [
+ "sessionID",
+ "assistantMessageID",
+ "id",
+ "metadata"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.tool.success": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.tool.success"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded_10"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "assistantMessageID": {
+ "$ref": "#/components/schemas/Session.Message.ID"
+ },
+ "id": {
+ "type": "string"
+ },
+ "content": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/components/schemas/Tool.Content"
+ }
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "executed": {
+ "type": "boolean"
+ },
+ "native": {
+ "$ref": "#/components/schemas/Session.Message.Native_11"
+ }
+ },
+ "required": [
+ "sessionID",
+ "assistantMessageID",
+ "id",
+ "content",
+ "executed"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.unarchived": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.unarchived"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ }
+ },
+ "required": [
+ "sessionID"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.usage.recorded": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.usage.recorded"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "source": {
+ "type": "string",
+ "enum": [
+ "title",
+ "compaction"
+ ]
+ },
+ "cost": {
+ "$ref": "#/components/schemas/Money.USD"
+ },
+ "tokens": {
+ "$ref": "#/components/schemas/TokenUsage.Info"
+ }
+ },
+ "required": [
+ "sessionID",
+ "source",
+ "cost",
+ "tokens"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.usage.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.usage.updated"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "cost": {
+ "$ref": "#/components/schemas/Money.USD"
+ },
+ "tokens": {
+ "$ref": "#/components/schemas/TokenUsage.Info"
+ }
+ },
+ "required": [
+ "sessionID",
+ "cost",
+ "tokens"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "session.viewed": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "session.viewed"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID"
+ },
+ "idle": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "sessionID",
+ "idle"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "shell.created": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "shell.created"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "info": {
+ "$ref": "#/components/schemas/Shell.Info"
+ }
+ },
+ "required": [
+ "info"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "shell.deleted": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "shell.deleted"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Shell.ID"
+ }
+ },
+ "required": [
+ "id"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "shell.exited": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "shell.exited"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Shell.ID"
+ },
+ "exit": {
+ "type": "number"
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "running",
+ "exited",
+ "timeout",
+ "killed"
+ ]
+ }
+ },
+ "required": [
+ "id",
+ "status"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "skill.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "skill.updated"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "not": {
+ "type": "null"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "tui.command.execute": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "tui.command.execute"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "command": {
+ "anyOf": [
+ {
+ "type": "string",
+ "enum": [
+ "session.list",
+ "session.new",
+ "session.interrupt",
+ "session.background",
+ "session.compact",
+ "session.page.up",
+ "session.page.down",
+ "session.line.up",
+ "session.line.down",
+ "session.half.page.up",
+ "session.half.page.down",
+ "session.first",
+ "session.last",
+ "prompt.clear",
+ "prompt.submit",
+ "agent.cycle"
+ ]
+ },
+ {
+ "type": "string"
+ }
+ ]
+ }
+ },
+ "required": [
+ "command"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "tui.prompt.append": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "tui.prompt.append"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "text": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "text"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "tui.session.select": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "tui.session.select"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "sessionID": {
+ "$ref": "#/components/schemas/Session.ID_1"
+ }
+ },
+ "required": [
+ "sessionID"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "tui.toast.show": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "tui.toast.show"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "title": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ },
+ "variant": {
+ "type": "string",
+ "enum": [
+ "info",
+ "success",
+ "warning",
+ "error"
+ ]
+ },
+ "duration": {
+ "anyOf": [
+ {
+ "type": "integer",
+ "exclusiveMinimum": 0
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "message",
+ "variant"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "vcs.branch.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "vcs.branch.updated"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "branch": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "websearch.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "websearch.updated"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "not": {
+ "type": "null"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "worktree.resolved": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "worktree.resolved"
+ ]
+ },
+ "durable": {
+ "type": "object",
+ "properties": {
+ "aggregateID": {
+ "type": "string"
+ },
+ "seq": {
+ "$ref": "#/components/schemas/Event.Seq"
+ },
+ "version": {
+ "$ref": "#/components/schemas/Event.VersionEncoded"
+ }
+ },
+ "required": [
+ "aggregateID",
+ "seq",
+ "version"
+ ],
+ "additionalProperties": true
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "projectID": {
+ "$ref": "#/components/schemas/Project.ID"
+ },
+ "directory": {
+ "$ref": "#/components/schemas/AbsolutePath"
+ },
+ "previous": {
+ "$ref": "#/components/schemas/Project.ID"
+ },
+ "adopted": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Project.ID"
+ }
+ }
+ },
+ "required": [
+ "projectID",
+ "directory",
+ "previous"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "durable",
+ "data"
+ ],
+ "additionalProperties": true
+ },
+ "worktree.updated": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/Event.ID"
+ },
+ "created": {
+ "type": "number"
+ },
+ "metadata": {
+ "type": "object"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "worktree.updated"
+ ]
+ },
+ "location": {
+ "$ref": "#/components/schemas/Location.Ref"
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "projectID": {
+ "$ref": "#/components/schemas/Project.ID"
+ }
+ },
+ "required": [
+ "projectID"
+ ],
+ "additionalProperties": true
+ }
+ },
+ "required": [
+ "id",
+ "created",
+ "type",
+ "data"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+}
diff --git a/scripts/utils/ruffen-docs.py b/scripts/utils/ruffen-docs.py
new file mode 100644
index 0000000..0cf2bd2
--- /dev/null
+++ b/scripts/utils/ruffen-docs.py
@@ -0,0 +1,167 @@
+# fork of https://github.com/asottile/blacken-docs adapted for ruff
+from __future__ import annotations
+
+import re
+import sys
+import argparse
+import textwrap
+import contextlib
+import subprocess
+from typing import Match, Optional, Sequence, Generator, NamedTuple, cast
+
+MD_RE = re.compile(
+ r"(?P^(?P *)```\s*python\n)" r"(?P.*?)" r"(?P^(?P=indent)```\s*$)",
+ re.DOTALL | re.MULTILINE,
+)
+MD_PYCON_RE = re.compile(
+ r"(?P^(?P *)```\s*pycon\n)" r"(?P.*?)" r"(?P^(?P=indent)```.*$)",
+ re.DOTALL | re.MULTILINE,
+)
+PYCON_PREFIX = ">>> "
+PYCON_CONTINUATION_PREFIX = "..."
+PYCON_CONTINUATION_RE = re.compile(
+ rf"^{re.escape(PYCON_CONTINUATION_PREFIX)}( |$)",
+)
+DEFAULT_LINE_LENGTH = 100
+
+
+class CodeBlockError(NamedTuple):
+ offset: int
+ exc: Exception
+
+
+def format_str(
+ src: str,
+) -> tuple[str, Sequence[CodeBlockError]]:
+ errors: list[CodeBlockError] = []
+
+ @contextlib.contextmanager
+ def _collect_error(match: Match[str]) -> Generator[None, None, None]:
+ try:
+ yield
+ except Exception as e:
+ errors.append(CodeBlockError(match.start(), e))
+
+ def _md_match(match: Match[str]) -> str:
+ code = textwrap.dedent(match["code"])
+ with _collect_error(match):
+ code = format_code_block(code)
+ code = textwrap.indent(code, match["indent"])
+ return f"{match['before']}{code}{match['after']}"
+
+ def _pycon_match(match: Match[str]) -> str:
+ code = ""
+ fragment = cast(Optional[str], None)
+
+ def finish_fragment() -> None:
+ nonlocal code
+ nonlocal fragment
+
+ if fragment is not None:
+ with _collect_error(match):
+ fragment = format_code_block(fragment)
+ fragment_lines = fragment.splitlines()
+ code += f"{PYCON_PREFIX}{fragment_lines[0]}\n"
+ for line in fragment_lines[1:]:
+ # Skip blank lines to handle Black adding a blank above
+ # functions within blocks. A blank line would end the REPL
+ # continuation prompt.
+ #
+ # >>> if True:
+ # ... def f():
+ # ... pass
+ # ...
+ if line:
+ code += f"{PYCON_CONTINUATION_PREFIX} {line}\n"
+ if fragment_lines[-1].startswith(" "):
+ code += f"{PYCON_CONTINUATION_PREFIX}\n"
+ fragment = None
+
+ indentation = None
+ for line in match["code"].splitlines():
+ orig_line, line = line, line.lstrip()
+ if indentation is None and line:
+ indentation = len(orig_line) - len(line)
+ continuation_match = PYCON_CONTINUATION_RE.match(line)
+ if continuation_match and fragment is not None:
+ fragment += line[continuation_match.end() :] + "\n"
+ else:
+ finish_fragment()
+ if line.startswith(PYCON_PREFIX):
+ fragment = line[len(PYCON_PREFIX) :] + "\n"
+ else:
+ code += orig_line[indentation:] + "\n"
+ finish_fragment()
+ return code
+
+ def _md_pycon_match(match: Match[str]) -> str:
+ code = _pycon_match(match)
+ code = textwrap.indent(code, match["indent"])
+ return f"{match['before']}{code}{match['after']}"
+
+ src = MD_RE.sub(_md_match, src)
+ src = MD_PYCON_RE.sub(_md_pycon_match, src)
+ return src, errors
+
+
+def format_code_block(code: str) -> str:
+ return subprocess.check_output(
+ [
+ sys.executable,
+ "-m",
+ "ruff",
+ "format",
+ "--stdin-filename=script.py",
+ f"--line-length={DEFAULT_LINE_LENGTH}",
+ ],
+ encoding="utf-8",
+ input=code,
+ )
+
+
+def format_file(
+ filename: str,
+ skip_errors: bool,
+) -> int:
+ with open(filename, encoding="UTF-8") as f:
+ contents = f.read()
+ new_contents, errors = format_str(contents)
+ for error in errors:
+ lineno = contents[: error.offset].count("\n") + 1
+ print(f"{filename}:{lineno}: code block parse error {error.exc}")
+ if errors and not skip_errors:
+ return 1
+ if contents != new_contents:
+ print(f"{filename}: Rewriting...")
+ with open(filename, "w", encoding="UTF-8") as f:
+ f.write(new_contents)
+ return 0
+ else:
+ return 0
+
+
+def main(argv: Sequence[str] | None = None) -> int:
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "-l",
+ "--line-length",
+ type=int,
+ default=DEFAULT_LINE_LENGTH,
+ )
+ parser.add_argument(
+ "-S",
+ "--skip-string-normalization",
+ action="store_true",
+ )
+ parser.add_argument("-E", "--skip-errors", action="store_true")
+ parser.add_argument("filenames", nargs="*")
+ args = parser.parse_args(argv)
+
+ retv = 0
+ for filename in args.filenames:
+ retv |= format_file(filename, skip_errors=args.skip_errors)
+ return retv
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/skills/opencode/SKILL.md b/skills/opencode/SKILL.md
new file mode 100644
index 0000000..ef5384d
--- /dev/null
+++ b/skills/opencode/SKILL.md
@@ -0,0 +1,575 @@
+---
+name: opencode
+description: Use the opencode Python SDK (version 0.0.1) to call the Opencode API: client setup, authentication, one example per resource, errors and retries.
+---
+
+# opencode 0.0.1
+
+This skill ships inside the `opencode` package, so it matches the installed version. The full method and type reference is `api.md` in the repository (https://github.com/).
+
+## Install
+
+```sh
+pip install opencode==0.0.1
+```
+
+## Authenticate and create a client
+
+Set `OPENCODE_PASSWORD`; the client reads it when `password` is not passed. Never hard-code the key.
+
+```python
+import os
+
+from opencode import Opencode
+
+client = Opencode(
+ password=os.environ.get("OPENCODE_PASSWORD"), # the default
+ max_retries=2, # the default; connection errors, 408, 409, 429 and 5xx are retried
+)
+```
+
+Use `AsyncOpencode` the same way, with `await` on every call. `OPENCODE_BASE_URL` overrides the base URL.
+
+## Examples
+
+One call per resource; the arguments are placeholders.
+
+### `client.agent.list` (GET /api/agent)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+agent = client.agent.list()
+print(agent)
+```
+
+### `client.command.list` (GET /api/command)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+command = client.command.list()
+print(command)
+```
+
+### `client.config.get` (GET /api/config)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+config = client.config.get()
+print(config)
+```
+
+### `client.credential.list` (GET /api/credential)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+credential = client.credential.list()
+print(credential)
+```
+
+### `client.debug.location.list` (GET /api/debug/location)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+location = client.debug.location.list()
+print(location)
+```
+
+### `client.event.subscribe` (GET /api/event)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+event = client.event.subscribe()
+print(event)
+```
+
+### `client.experimental.persistent_pty.handoff` (POST /api/experimental/persistent-pty/handoff)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+persistent_pty = client.experimental.persistent_pty.handoff()
+print(persistent_pty)
+```
+
+### `client.file.list` (GET /api/fs/list)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+file = client.file.list()
+print(file)
+```
+
+### `client.form.list` (GET /api/form)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+form = client.form.list()
+print(form)
+```
+
+### `client.generate.text` (POST /api/experimental/generate)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+generate = client.generate.text(prompt="prompt")
+print(generate)
+```
+
+### `client.integration.list` (GET /api/integration)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+integration = client.integration.list()
+print(integration)
+```
+
+### `client.integration.wellknown.add` (POST /api/experimental/integration/wellknown)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+wellknown = client.integration.wellknown.add(url="url")
+print(wellknown)
+```
+
+### `client.integration.connect.external` (POST /api/integration/{integrationID}/connect/external)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+connect = client.integration.connect.external(integration_id={}, method_id={})
+print(connect)
+```
+
+### `client.integration.oauth.cancel` (DELETE /api/integration/{integrationID}/connect/oauth/{attemptID})
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+oauth = client.integration.oauth.cancel(integration_id={}, attempt_id={})
+print(oauth)
+```
+
+### `client.integration.command.cancel` (DELETE /api/integration/{integrationID}/connect/command/{attemptID})
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+command = client.integration.command.cancel(integration_id={}, attempt_id={})
+print(command)
+```
+
+### `client.location.get` (GET /api/location)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+location = client.location.get()
+print(location)
+```
+
+### `client.mcp.list` (GET /api/mcp)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+mcp = client.mcp.list()
+print(mcp)
+```
+
+### `client.mcp.resource.catalog` (GET /api/mcp/resource)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+resource = client.mcp.resource.catalog()
+print(resource)
+```
+
+### `client.message.list` (GET /api/session/{sessionID}/message)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+message = client.message.list(session_id={})
+print(message)
+```
+
+### `client.migration.v1.status` (GET /api/experimental/migration/v1)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+v1 = client.migration.v1.status()
+print(v1)
+```
+
+### `client.model.list` (GET /api/model)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+model = client.model.list()
+print(model)
+```
+
+### `client.permission.create` (POST /api/session/{sessionID}/permission)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+permission = client.permission.create(session_id={}, action="action", resources=["string"])
+print(permission)
+```
+
+### `client.permission.request.list` (GET /api/permission/request)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+request = client.permission.request.list()
+print(request)
+```
+
+### `client.permission.saved.list` (GET /api/permission/saved)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+saved = client.permission.saved.list()
+print(saved)
+```
+
+### `client.plugin.list` (GET /api/plugin)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+plugin = client.plugin.list()
+print(plugin)
+```
+
+### `client.project.list` (GET /api/project)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+project = client.project.list()
+print(project)
+```
+
+### `client.provider.list` (GET /api/provider)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+provider = client.provider.list()
+print(provider)
+```
+
+### `client.pty.create` (POST /api/pty)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+pty = client.pty.create()
+print(pty)
+```
+
+### `client.pty.connect.token` (POST /api/pty/{ptyID}/connect-token)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+connect = client.pty.connect.token(pty_id={})
+print(connect)
+```
+
+### `client.reference.list` (GET /api/reference)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+reference = client.reference.list()
+print(reference)
+```
+
+### `client.rpc.call` (POST /api/rpc/{rpcID}/{method})
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+rpc = client.rpc.call(rpc_id="rpcID", method="method")
+print(rpc)
+```
+
+### `client.server.info` (GET /api/info)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+server = client.server.info()
+print(server)
+```
+
+### `client.session.create` (POST /api/session)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+session = client.session.create()
+print(session)
+```
+
+### `client.session.revert.clear` (DELETE /api/session/{sessionID}/revert)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+revert = client.session.revert.clear(session_id={})
+print(revert)
+```
+
+### `client.session.inbox.update` (PATCH /api/session/{sessionID}/inbox/{inboxID})
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+inbox = client.session.inbox.update(session_id={}, inbox_id={}, delivery={})
+print(inbox)
+```
+
+### `client.session.instructions.entry.list` (GET /api/experimental/session/{sessionID}/instructions/entries)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+entry = client.session.instructions.entry.list(session_id={})
+print(entry)
+```
+
+### `client.session.message.get` (GET /api/session/{sessionID}/message/{messageID})
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+message = client.session.message.get(session_id={}, message_id={})
+print(message)
+```
+
+### `client.session.form.create` (POST /api/session/{sessionID}/form)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+form = client.session.form.create(session_id="sessionID", fields={}, title="title")
+print(form)
+```
+
+### `client.shell.list` (GET /api/shell)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+shell = client.shell.list()
+print(shell)
+```
+
+### `client.skill.list` (GET /api/skill)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+skill = client.skill.list()
+print(skill)
+```
+
+### `client.vcs.base` (GET /api/vcs/base)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+vcs = client.vcs.base()
+print(vcs)
+```
+
+### `client.vcs.branch.list` (GET /api/vcs/branch)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+branch = client.vcs.branch.list()
+print(branch)
+```
+
+### `client.voice.session` (POST /api/experimental/voice/session)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+voice = client.voice.session(model="model", transport={})
+print(voice)
+```
+
+### `client.websearch.providers` (GET /api/websearch/provider)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+websearch = client.websearch.providers()
+print(websearch)
+```
+
+### `client.worktree.create` (POST /api/worktree)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+worktree = client.worktree.create(project_id={})
+print(worktree)
+```
+
+## Pagination
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+# Iterating the page fetches the next pages as needed.
+for item in client.message.list(session_id={}):
+ print(item)
+```
+
+## Streaming
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+stream = client.event.subscribe()
+for event in stream:
+ print(event)
+```
+
+## Errors
+
+```python
+import opencode
+from opencode import Opencode
+
+client = Opencode()
+
+try:
+ agent = client.agent.list()
+ print(agent)
+except opencode.APIConnectionError as e:
+ print("The server could not be reached:", e.__cause__)
+except opencode.RateLimitError:
+ print("Rate limited (429); back off and retry.")
+except opencode.APIStatusError as e:
+ print("Non-2xx response:", e.status_code, e.response)
+```
+
+Every method also has `.with_raw_response` (headers and the raw response) and `.with_streaming_response`.
diff --git a/src/opencode/__init__.py b/src/opencode/__init__.py
index 7c3b309..3e5b0a5 100644
--- a/src/opencode/__init__.py
+++ b/src/opencode/__init__.py
@@ -1,235 +1,102 @@
-"""Typed Python client for the OpenCode 2.x server API.
-
-Quick start::
-
- from opencode import Opencode, OpencodeServer
-
- with OpencodeServer() as server, Opencode(server.url, password=server.password) as client:
- session = client.session.create(agent="build")
- client.session.prompt(session.id, text="Summarize this repository")
- client.session.wait(session.id)
- for message in client.message.list(session.id):
- print(message.type)
-"""
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING, Any
-
-from . import service
-from ._aiohttp import DefaultAioHttpClient
-from ._client import AsyncOpencode, Opencode
-
-#
-from ._errors import (
- AgentNotFoundError,
- CommandExecutionError,
- CommandNotFoundError,
- ForbiddenError,
- FormAlreadySettledError,
- FormInvalidAnswerError,
- FormNotFoundError,
- InstructionEntryValueTooLargeError,
- IntegrationAttemptNotFoundError,
- IntegrationMethodNotFoundError,
- IntegrationNotFoundError,
- InvalidCursorError,
- InvalidRequestError,
- LocationNotFoundError,
- McpServerNotFoundError,
- MessageNotFoundError,
- PermissionNotFoundError,
- PermissionSavedNotFoundError,
- ProjectNotFoundError,
- ProviderNotFoundError,
- PtyNotFoundError,
- RemoteFileNotFoundError,
- RpcError,
- RpcInternalError,
- SessionBusyError,
- SessionNotFoundError,
- ShellNotFoundError,
- SkillNotFoundError,
- UnauthorizedError,
- UnknownError,
- VcsInitNotSupportedError,
- WorktreeError,
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+import typing as _t
+
+from . import types
+from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given
+from ._utils import file_from_path
+from ._client import (
+ Client,
+ Stream,
+ Timeout,
+ Opencode,
+ Transport,
+ AsyncClient,
+ AsyncStream,
+ AsyncOpencode,
+ RequestOptions,
)
-
-#
+from ._models import BaseModel
+from ._version import __title__, __version__
+from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse
+from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS
from ._exceptions import (
- APIConnectionError,
APIError,
- APIResponseValidationError,
+ ConflictError,
+ NotFoundError,
+ OpencodeError,
APIStatusError,
+ RateLimitError,
APITimeoutError,
- AuthenticationError,
BadRequestError,
- ConflictError,
- ContentTooLargeError,
+ APIConnectionError,
+ AuthenticationError,
InternalServerError,
- NotFoundError,
- NotImplementedServerError,
- OpencodeError,
PermissionDeniedError,
- RateLimitError,
- ServiceUnavailableError,
UnprocessableEntityError,
+ APIResponseValidationError,
)
-from ._logs import setup_logging as _setup_logging
-from ._models import BaseModel
-from ._pagination import AsyncCursorPage, AsyncPaginator, SyncCursorPage
-from ._pty import AsyncPtyConnection, PtyConnection, PtyConnectionError
-from ._response import APIResponse, AsyncAPIResponse
-from ._server import AsyncOpencodeServer, OpencodeServer, ServerStartError
-from ._service import Endpoint, ServiceError
-from ._streaming import AsyncStream, Stream
-from ._types import NOT_GIVEN, NotGiven
-from ._version import __version__
-
-# isort: split
-# Hand-written helpers, kept apart from the generated error-import block above.
-from ._events import (
- SUBSCRIBER_CAPACITY,
- AsyncEventHub,
- AsyncSubscription,
- EventHub,
- EventOverflowError,
- Reconnect,
- Subscription,
- session_id_of,
-)
-from ._rpc import (
- AsyncRpcClient,
- AsyncRpcEvents,
- RpcCallError,
- RpcCallInternalError,
- RpcClient,
- RpcDefinition,
- RpcEventDefinition,
- RpcEventPayload,
- RpcEvents,
- RpcMethod,
- rpc_event,
- rpc_method,
-)
-
-if TYPE_CHECKING:
- from . import types
-
-#
-API_VERSION = "2.1.2"
-"""The OpenCode server version this client was generated from."""
-#
+from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient
+from ._utils._logs import setup_logging as _setup_logging
__all__ = [
- "API_VERSION",
+ "types",
+ "__version__",
+ "__title__",
+ "NoneType",
+ "Transport",
+ "ProxiesTypes",
+ "NotGiven",
"NOT_GIVEN",
- "APIConnectionError",
- "APIResponse",
+ "not_given",
+ "Omit",
+ "omit",
+ "OpencodeError",
"APIError",
- "APIResponseValidationError",
"APIStatusError",
"APITimeoutError",
- "AsyncAPIResponse",
- "AsyncCursorPage",
- "AsyncOpencode",
- "AsyncOpencodeServer",
- "AsyncPaginator",
- "AsyncPtyConnection",
- "AsyncStream",
- "AuthenticationError",
+ "APIConnectionError",
+ "APIResponseValidationError",
"BadRequestError",
- "BaseModel",
- "ConflictError",
- "ContentTooLargeError",
- "DefaultAioHttpClient",
- "Endpoint",
- "InternalServerError",
- "NotFoundError",
- "NotGiven",
- "NotImplementedServerError",
- "Opencode",
- "OpencodeError",
- "OpencodeServer",
+ "AuthenticationError",
"PermissionDeniedError",
- "PtyConnection",
- "PtyConnectionError",
+ "NotFoundError",
+ "ConflictError",
+ "UnprocessableEntityError",
"RateLimitError",
- "ServerStartError",
- "ServiceError",
- "ServiceUnavailableError",
+ "InternalServerError",
+ "Timeout",
+ "RequestOptions",
+ "Client",
+ "AsyncClient",
"Stream",
- "SyncCursorPage",
- "UnprocessableEntityError",
- "__version__",
- "service",
- "types",
- # Shared event stream and typed plugin RPC
- "SUBSCRIBER_CAPACITY",
- "AsyncEventHub",
- "AsyncRpcClient",
- "AsyncRpcEvents",
- "AsyncSubscription",
- "EventHub",
- "EventOverflowError",
- "Reconnect",
- "RpcCallError",
- "RpcCallInternalError",
- "RpcClient",
- "RpcDefinition",
- "RpcEventDefinition",
- "RpcEventPayload",
- "RpcEvents",
- "RpcMethod",
- "Subscription",
- "rpc_event",
- "rpc_method",
- "session_id_of",
- #
- "AgentNotFoundError",
- "CommandExecutionError",
- "CommandNotFoundError",
- "RemoteFileNotFoundError",
- "ForbiddenError",
- "FormAlreadySettledError",
- "FormInvalidAnswerError",
- "FormNotFoundError",
- "InstructionEntryValueTooLargeError",
- "IntegrationAttemptNotFoundError",
- "IntegrationMethodNotFoundError",
- "IntegrationNotFoundError",
- "InvalidCursorError",
- "InvalidRequestError",
- "LocationNotFoundError",
- "McpServerNotFoundError",
- "MessageNotFoundError",
- "PermissionNotFoundError",
- "PermissionSavedNotFoundError",
- "ProjectNotFoundError",
- "ProviderNotFoundError",
- "PtyNotFoundError",
- "RpcError",
- "RpcInternalError",
- "SessionBusyError",
- "SessionNotFoundError",
- "ShellNotFoundError",
- "SkillNotFoundError",
- "UnauthorizedError",
- "UnknownError",
- "VcsInitNotSupportedError",
- "WorktreeError",
- #
+ "AsyncStream",
+ "Opencode",
+ "AsyncOpencode",
+ "file_from_path",
+ "BaseModel",
+ "DEFAULT_TIMEOUT",
+ "DEFAULT_MAX_RETRIES",
+ "DEFAULT_CONNECTION_LIMITS",
+ "DefaultHttpxClient",
+ "DefaultAsyncHttpxClient",
+ "DefaultAioHttpClient",
]
-
-def __getattr__(name: str) -> Any:
- # `opencode.types` builds several hundred models, so it is imported on first use rather than with the package.
- if name == "types":
- import importlib
-
- return importlib.import_module(".types", __name__)
- raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
-
+if not _t.TYPE_CHECKING:
+ from ._utils._resources_proxy import resources as resources
_setup_logging()
+
+# Update the __module__ attribute for exported symbols so that
+# error messages point to this module instead of the module
+# it was originally defined in, e.g.
+# opencode._exceptions.NotFoundError -> opencode.NotFoundError
+__locals = locals()
+for __name in __all__:
+ if not __name.startswith("__"):
+ try:
+ __locals[__name].__module__ = "opencode"
+ except (TypeError, AttributeError):
+ # Some of our exported symbols are builtins which we can't set attributes for.
+ pass
diff --git a/src/opencode/_agent/AGENTS.md b/src/opencode/_agent/AGENTS.md
new file mode 100644
index 0000000..a4fc8ec
--- /dev/null
+++ b/src/opencode/_agent/AGENTS.md
@@ -0,0 +1,9 @@
+# Using opencode 0.0.1
+
+Instructions for coding agents writing code that uses the `opencode` package.
+
+- Read `SKILL.md` next to this file first: install, authentication (`OPENCODE_PASSWORD`), one example per resource and error handling, for this exact version.
+- Look up method signatures and types in `api.md` (https://github.com//blob/main/api.md); every request and response type is importable from `opencode.types`.
+- Construct one `Opencode` (or `AsyncOpencode`) and reuse it; do not pass secrets in code.
+- Catch `opencode.APIError` subclasses rather than `httpx` errors; retries and timeouts are already built in.
+- Do not edit generated files of the SDK; they are regenerated from the API spec.
diff --git a/src/opencode/_agent/SKILL.md b/src/opencode/_agent/SKILL.md
new file mode 100644
index 0000000..ef5384d
--- /dev/null
+++ b/src/opencode/_agent/SKILL.md
@@ -0,0 +1,575 @@
+---
+name: opencode
+description: Use the opencode Python SDK (version 0.0.1) to call the Opencode API: client setup, authentication, one example per resource, errors and retries.
+---
+
+# opencode 0.0.1
+
+This skill ships inside the `opencode` package, so it matches the installed version. The full method and type reference is `api.md` in the repository (https://github.com/).
+
+## Install
+
+```sh
+pip install opencode==0.0.1
+```
+
+## Authenticate and create a client
+
+Set `OPENCODE_PASSWORD`; the client reads it when `password` is not passed. Never hard-code the key.
+
+```python
+import os
+
+from opencode import Opencode
+
+client = Opencode(
+ password=os.environ.get("OPENCODE_PASSWORD"), # the default
+ max_retries=2, # the default; connection errors, 408, 409, 429 and 5xx are retried
+)
+```
+
+Use `AsyncOpencode` the same way, with `await` on every call. `OPENCODE_BASE_URL` overrides the base URL.
+
+## Examples
+
+One call per resource; the arguments are placeholders.
+
+### `client.agent.list` (GET /api/agent)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+agent = client.agent.list()
+print(agent)
+```
+
+### `client.command.list` (GET /api/command)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+command = client.command.list()
+print(command)
+```
+
+### `client.config.get` (GET /api/config)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+config = client.config.get()
+print(config)
+```
+
+### `client.credential.list` (GET /api/credential)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+credential = client.credential.list()
+print(credential)
+```
+
+### `client.debug.location.list` (GET /api/debug/location)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+location = client.debug.location.list()
+print(location)
+```
+
+### `client.event.subscribe` (GET /api/event)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+event = client.event.subscribe()
+print(event)
+```
+
+### `client.experimental.persistent_pty.handoff` (POST /api/experimental/persistent-pty/handoff)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+persistent_pty = client.experimental.persistent_pty.handoff()
+print(persistent_pty)
+```
+
+### `client.file.list` (GET /api/fs/list)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+file = client.file.list()
+print(file)
+```
+
+### `client.form.list` (GET /api/form)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+form = client.form.list()
+print(form)
+```
+
+### `client.generate.text` (POST /api/experimental/generate)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+generate = client.generate.text(prompt="prompt")
+print(generate)
+```
+
+### `client.integration.list` (GET /api/integration)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+integration = client.integration.list()
+print(integration)
+```
+
+### `client.integration.wellknown.add` (POST /api/experimental/integration/wellknown)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+wellknown = client.integration.wellknown.add(url="url")
+print(wellknown)
+```
+
+### `client.integration.connect.external` (POST /api/integration/{integrationID}/connect/external)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+connect = client.integration.connect.external(integration_id={}, method_id={})
+print(connect)
+```
+
+### `client.integration.oauth.cancel` (DELETE /api/integration/{integrationID}/connect/oauth/{attemptID})
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+oauth = client.integration.oauth.cancel(integration_id={}, attempt_id={})
+print(oauth)
+```
+
+### `client.integration.command.cancel` (DELETE /api/integration/{integrationID}/connect/command/{attemptID})
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+command = client.integration.command.cancel(integration_id={}, attempt_id={})
+print(command)
+```
+
+### `client.location.get` (GET /api/location)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+location = client.location.get()
+print(location)
+```
+
+### `client.mcp.list` (GET /api/mcp)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+mcp = client.mcp.list()
+print(mcp)
+```
+
+### `client.mcp.resource.catalog` (GET /api/mcp/resource)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+resource = client.mcp.resource.catalog()
+print(resource)
+```
+
+### `client.message.list` (GET /api/session/{sessionID}/message)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+message = client.message.list(session_id={})
+print(message)
+```
+
+### `client.migration.v1.status` (GET /api/experimental/migration/v1)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+v1 = client.migration.v1.status()
+print(v1)
+```
+
+### `client.model.list` (GET /api/model)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+model = client.model.list()
+print(model)
+```
+
+### `client.permission.create` (POST /api/session/{sessionID}/permission)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+permission = client.permission.create(session_id={}, action="action", resources=["string"])
+print(permission)
+```
+
+### `client.permission.request.list` (GET /api/permission/request)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+request = client.permission.request.list()
+print(request)
+```
+
+### `client.permission.saved.list` (GET /api/permission/saved)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+saved = client.permission.saved.list()
+print(saved)
+```
+
+### `client.plugin.list` (GET /api/plugin)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+plugin = client.plugin.list()
+print(plugin)
+```
+
+### `client.project.list` (GET /api/project)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+project = client.project.list()
+print(project)
+```
+
+### `client.provider.list` (GET /api/provider)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+provider = client.provider.list()
+print(provider)
+```
+
+### `client.pty.create` (POST /api/pty)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+pty = client.pty.create()
+print(pty)
+```
+
+### `client.pty.connect.token` (POST /api/pty/{ptyID}/connect-token)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+connect = client.pty.connect.token(pty_id={})
+print(connect)
+```
+
+### `client.reference.list` (GET /api/reference)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+reference = client.reference.list()
+print(reference)
+```
+
+### `client.rpc.call` (POST /api/rpc/{rpcID}/{method})
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+rpc = client.rpc.call(rpc_id="rpcID", method="method")
+print(rpc)
+```
+
+### `client.server.info` (GET /api/info)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+server = client.server.info()
+print(server)
+```
+
+### `client.session.create` (POST /api/session)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+session = client.session.create()
+print(session)
+```
+
+### `client.session.revert.clear` (DELETE /api/session/{sessionID}/revert)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+revert = client.session.revert.clear(session_id={})
+print(revert)
+```
+
+### `client.session.inbox.update` (PATCH /api/session/{sessionID}/inbox/{inboxID})
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+inbox = client.session.inbox.update(session_id={}, inbox_id={}, delivery={})
+print(inbox)
+```
+
+### `client.session.instructions.entry.list` (GET /api/experimental/session/{sessionID}/instructions/entries)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+entry = client.session.instructions.entry.list(session_id={})
+print(entry)
+```
+
+### `client.session.message.get` (GET /api/session/{sessionID}/message/{messageID})
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+message = client.session.message.get(session_id={}, message_id={})
+print(message)
+```
+
+### `client.session.form.create` (POST /api/session/{sessionID}/form)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+form = client.session.form.create(session_id="sessionID", fields={}, title="title")
+print(form)
+```
+
+### `client.shell.list` (GET /api/shell)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+shell = client.shell.list()
+print(shell)
+```
+
+### `client.skill.list` (GET /api/skill)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+skill = client.skill.list()
+print(skill)
+```
+
+### `client.vcs.base` (GET /api/vcs/base)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+vcs = client.vcs.base()
+print(vcs)
+```
+
+### `client.vcs.branch.list` (GET /api/vcs/branch)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+branch = client.vcs.branch.list()
+print(branch)
+```
+
+### `client.voice.session` (POST /api/experimental/voice/session)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+voice = client.voice.session(model="model", transport={})
+print(voice)
+```
+
+### `client.websearch.providers` (GET /api/websearch/provider)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+websearch = client.websearch.providers()
+print(websearch)
+```
+
+### `client.worktree.create` (POST /api/worktree)
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+worktree = client.worktree.create(project_id={})
+print(worktree)
+```
+
+## Pagination
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+# Iterating the page fetches the next pages as needed.
+for item in client.message.list(session_id={}):
+ print(item)
+```
+
+## Streaming
+
+```python
+from opencode import Opencode
+
+client = Opencode()
+
+stream = client.event.subscribe()
+for event in stream:
+ print(event)
+```
+
+## Errors
+
+```python
+import opencode
+from opencode import Opencode
+
+client = Opencode()
+
+try:
+ agent = client.agent.list()
+ print(agent)
+except opencode.APIConnectionError as e:
+ print("The server could not be reached:", e.__cause__)
+except opencode.RateLimitError:
+ print("Rate limited (429); back off and retry.")
+except opencode.APIStatusError as e:
+ print("Non-2xx response:", e.status_code, e.response)
+```
+
+Every method also has `.with_raw_response` (headers and the raw response) and `.with_streaming_response`.
diff --git a/src/opencode/_agent/llms.txt b/src/opencode/_agent/llms.txt
new file mode 100644
index 0000000..fee10a6
--- /dev/null
+++ b/src/opencode/_agent/llms.txt
@@ -0,0 +1,9 @@
+# opencode
+
+> Python SDK for the Opencode API, version 0.0.1. Install with `pip install opencode` and import `opencode`.
+
+## Docs
+
+- [README](https://github.com//blob/main/README.md): installation, usage, async client, retries, timeouts
+- [api.md](https://github.com//blob/main/api.md): every resource, method and type
+- [SKILL.md](https://github.com//blob/main/src/opencode/_agent/SKILL.md): agent skill with tested examples
diff --git a/src/opencode/_base_client.py b/src/opencode/_base_client.py
index 2e81b9e..d0e89f0 100644
--- a/src/opencode/_base_client.py
+++ b/src/opencode/_base_client.py
@@ -1,765 +1,2131 @@
-"""HTTP transport shared by the sync and async clients.
-
-This module is the only place that talks to httpx. It builds requests, applies authentication, retries
-transient failures, and turns responses into typed values or errors.
-"""
-
from __future__ import annotations
-import base64
-import builtins
-import email.utils
-import functools
+import sys
import json
+import time
+import uuid
+import email
+import asyncio
+import inspect
import logging
-import os
import platform
-import random
-import time
-from dataclasses import dataclass, field
-from typing import TYPE_CHECKING, Any, Literal, TypeVar, cast
+import warnings
+import email.utils
+from types import TracebackType
+from random import random
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Dict,
+ Type,
+ Union,
+ Generic,
+ Mapping,
+ TypeVar,
+ Iterable,
+ Iterator,
+ Optional,
+ Generator,
+ AsyncIterator,
+ cast,
+ overload,
+)
+from typing_extensions import Literal, override, get_origin
import anyio
import httpx
+import distro
import pydantic
-from typing_extensions import Self, override
-
-from . import _constants as constants
-from ._errors import TAGGED_ERRORS
+from httpx import URL
+from pydantic import PrivateAttr
+
+from . import _exceptions
+from ._qs import Querystring
+from ._files import to_httpx_files, async_to_httpx_files
+from ._types import (
+ Body,
+ Omit,
+ Query,
+ Headers,
+ Timeout,
+ NotGiven,
+ ResponseT,
+ AnyMapping,
+ PostParser,
+ BinaryTypes,
+ RequestFiles,
+ HttpxSendArgs,
+ RequestOptions,
+ AsyncBinaryTypes,
+ HttpxRequestFiles,
+ ModelBuilderProtocol,
+ not_given,
+)
+from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping
+from ._compat import PYDANTIC_V1, model_copy, model_dump
+from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type
+from ._response import (
+ APIResponse,
+ BaseAPIResponse,
+ AsyncAPIResponse,
+ extract_response_type,
+)
+from ._constants import (
+ DEFAULT_TIMEOUT,
+ MAX_RETRY_DELAY,
+ DEFAULT_MAX_RETRIES,
+ INITIAL_RETRY_DELAY,
+ RAW_RESPONSE_HEADER,
+ OVERRIDE_CAST_TO_HEADER,
+ DEFAULT_CONNECTION_LIMITS,
+)
+from ._streaming import Stream, SSEDecoder, AsyncStream, SSEBytesDecoder
from ._exceptions import (
- APIConnectionError,
- APIResponseValidationError,
APIStatusError,
APITimeoutError,
- OpencodeError,
- error_message,
- status_error_class,
- validation_summary,
+ APIConnectionError,
+ APIResponseValidationError,
)
-from ._qs import encode_query, to_jsonable
-from ._response import APIResponse, AsyncAPIResponse, raw_mode
-from ._streaming import AsyncStream, Stream
-from ._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-from ._version import __version__
+from ._utils._json import openapi_dumps
+
+log: logging.Logger = logging.getLogger(__name__)
+
+# TODO: make base page type vars covariant
+SyncPageT = TypeVar("SyncPageT", bound="BaseSyncPage[Any]")
+AsyncPageT = TypeVar("AsyncPageT", bound="BaseAsyncPage[Any]")
+
+
+_T = TypeVar("_T")
+_T_co = TypeVar("_T_co", covariant=True)
+
+_StreamT = TypeVar("_StreamT", bound=Stream[Any])
+_AsyncStreamT = TypeVar("_AsyncStreamT", bound=AsyncStream[Any])
if TYPE_CHECKING:
- from collections.abc import Callable, Coroutine, Mapping
- from types import TracebackType
+ from httpx._config import (
+ DEFAULT_TIMEOUT_CONFIG, # pyright: ignore[reportPrivateImportUsage]
+ )
+
+ HTTPX_DEFAULT_TIMEOUT = DEFAULT_TIMEOUT_CONFIG
+else:
+ try:
+ from httpx._config import DEFAULT_TIMEOUT_CONFIG as HTTPX_DEFAULT_TIMEOUT
+ except ImportError:
+ # taken from https://github.com/encode/httpx/blob/3ba5fe0d7ac70222590e759c31442b1cab263791/httpx/_config.py#L366
+ HTTPX_DEFAULT_TIMEOUT = Timeout(5.0)
- from ._pagination import AsyncPaginator, SyncCursorPage
-__all__ = ["AsyncAPIClient", "SyncAPIClient"]
+class PageInfo:
+ """Stores the necessary information to build the request to retrieve the next page.
-log = logging.getLogger("opencode")
+ Either `url` or `params` must be set.
+ """
-_T = TypeVar("_T")
+ url: URL | NotGiven
+ params: Query | NotGiven
+ json: Body | NotGiven
+
+ @overload
+ def __init__(
+ self,
+ *,
+ url: URL,
+ ) -> None: ...
+
+ @overload
+ def __init__(
+ self,
+ *,
+ params: Query,
+ ) -> None: ...
-ResponseKind = Literal["json", "void", "binary"]
+ @overload
+ def __init__(
+ self,
+ *,
+ json: Body,
+ ) -> None: ...
+ def __init__(
+ self,
+ *,
+ url: URL | NotGiven = not_given,
+ json: Body | NotGiven = not_given,
+ params: Query | NotGiven = not_given,
+ ) -> None:
+ self.url = url
+ self.json = json
+ self.params = params
-@functools.cache
-def _cached_adapter(tp: Any) -> pydantic.TypeAdapter[Any]:
- return pydantic.TypeAdapter(tp)
+ @override
+ def __repr__(self) -> str:
+ if self.url:
+ return f"{self.__class__.__name__}(url={self.url})"
+ if self.json:
+ return f"{self.__class__.__name__}(json={self.json})"
+ return f"{self.__class__.__name__}(params={self.params})"
-@functools.cache
-def _resolve_type(expr: str) -> Any:
- # Generated resources name their response types as strings, so `import opencode` does not build every model.
- from . import types
+class BasePage(GenericModel, Generic[_T]):
+ """
+ Defines the core interface for pagination.
- return eval(expr, {**vars(types), "builtins": builtins}) # expressions come from the generator
+ Type Args:
+ ModelT: The pydantic model that represents an item in the response.
+ Methods:
+ has_next_page(): Check if there is another page available
+ next_page_info(): Get the necessary information to make a request for the next page
+ """
-def type_adapter(tp: Any) -> pydantic.TypeAdapter[Any]:
- """Returns a cached ``TypeAdapter`` for a response type, or for a type expression over ``opencode.types``."""
- if isinstance(tp, str):
- tp = _resolve_type(tp)
- try:
- return _cached_adapter(tp)
- except TypeError: # unhashable type expression
- return pydantic.TypeAdapter(tp)
-
-
-@dataclass
-class RequestOptions:
- """Everything needed to send one request. Generated methods build these."""
-
- method: str
- path: str
- query: Mapping[str, object] = field(default_factory=dict[str, object])
- headers: Mapping[str, object] = field(default_factory=dict[str, object])
- body: object | NotGiven = NOT_GIVEN
- content: bytes | None = None
- extra_headers: Headers | None = None
- extra_query: Query | None = None
- extra_body: Body | None = None
- timeout: Timeout | NotGiven = NOT_GIVEN
- stream: bool = False
+ _options: FinalRequestOptions = PrivateAttr()
+ _model: Type[_T] = PrivateAttr()
- @property
- def idempotent(self) -> bool:
- return self.method.upper() in constants.IDEMPOTENT_METHODS
+ def has_next_page(self) -> bool:
+ items = self._get_page_items()
+ if not items:
+ return False
+ return self.next_page_info() is not None
+ def next_page_info(self) -> Optional[PageInfo]: ...
-def _platform_headers() -> dict[str, str]:
- return {
- "User-Agent": f"opencode-python/{__version__} python/{platform.python_version()} httpx/{httpx.__version__}",
- }
+ def _get_page_items(self) -> Iterable[_T]: # type: ignore[empty-body]
+ ...
+
+ def _params_from_url(self, url: URL) -> httpx.QueryParams:
+ # TODO: do we have to preprocess params here?
+ return httpx.QueryParams(cast(Any, self._options.params)).merge(url.params)
+
+ def _info_to_options(self, info: PageInfo) -> FinalRequestOptions:
+ options = model_copy(self._options)
+ options._strip_raw_response_header()
+
+ if not isinstance(info.params, NotGiven):
+ options.params = {**options.params, **info.params}
+ return options
+
+ if not isinstance(info.url, NotGiven):
+ params = self._params_from_url(info.url)
+ url = info.url.copy_with(params=params)
+ options.params = dict(url.params)
+ options.url = str(url)
+ return options
+
+ if not isinstance(info.json, NotGiven):
+ if not is_mapping(info.json):
+ raise TypeError("Pagination is only supported with mappings")
+
+ if not options.json_data:
+ options.json_data = {**info.json}
+ else:
+ if not is_mapping(options.json_data):
+ raise TypeError("Pagination is only supported with mappings")
+ options.json_data = {**options.json_data, **info.json}
+ return options
-def _resolve_password(password: str | None) -> str | None:
- if password is not None:
- return password or None
- for name in constants.PASSWORD_ENVS:
- value = os.environ.get(name)
- if value:
- return value
- return None
+ raise ValueError("Unexpected PageInfo state")
+
+
+class BaseSyncPage(BasePage[_T], Generic[_T]):
+ _client: SyncAPIClient = pydantic.PrivateAttr()
+
+ def _set_private_attributes(
+ self,
+ client: SyncAPIClient,
+ model: Type[_T],
+ options: FinalRequestOptions,
+ ) -> None:
+ if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None:
+ self.__pydantic_private__ = {}
+
+ self._model = model
+ self._client = client
+ self._options = options
+
+ # Pydantic uses a custom `__iter__` method to support casting BaseModels
+ # to dictionaries. e.g. dict(model).
+ # As we want to support `for item in page`, this is inherently incompatible
+ # with the default pydantic behaviour. It is not possible to support both
+ # use cases at once. Fortunately, this is not a big deal as all other pydantic
+ # methods should continue to work as expected as there is an alternative method
+ # to cast a model to a dictionary, model.dict(), which is used internally
+ # by pydantic.
+ def __iter__(self) -> Iterator[_T]: # type: ignore
+ for page in self.iter_pages():
+ for item in page._get_page_items():
+ yield item
+
+ def iter_pages(self: SyncPageT) -> Iterator[SyncPageT]:
+ page = self
+ while True:
+ yield page
+ if page.has_next_page():
+ page = page.get_next_page()
+ else:
+ return
+
+ def get_next_page(self: SyncPageT) -> SyncPageT:
+ info = self.next_page_info()
+ if not info:
+ raise RuntimeError(
+ "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`."
+ )
+ options = self._info_to_options(info)
+ return self._client._request_api_list(self._model, page=self.__class__, options=options)
-def _resolve_base_url(base_url: str | httpx.URL | None) -> httpx.URL:
- if base_url is None:
- base_url = os.environ.get(constants.BASE_URL_ENV)
- if not base_url:
- raise OpencodeError(
- "No OpenCode server URL. Pass base_url=..., set the OPENCODE_BASE_URL environment variable, "
- "or start a server with opencode.OpencodeServer()."
+
+class AsyncPaginator(Generic[_T, AsyncPageT]):
+ def __init__(
+ self,
+ client: AsyncAPIClient,
+ options: FinalRequestOptions,
+ page_cls: Type[AsyncPageT],
+ model: Type[_T],
+ ) -> None:
+ self._model = model
+ self._client = client
+ self._options = options
+ self._page_cls = page_cls
+
+ def __await__(self) -> Generator[Any, None, AsyncPageT]:
+ return self._get_page().__await__()
+
+ async def _get_page(self) -> AsyncPageT:
+ def _parser(resp: AsyncPageT) -> AsyncPageT:
+ resp._set_private_attributes(
+ model=self._model,
+ options=self._options,
+ client=self._client,
+ )
+ return resp
+
+ self._options.post_parser = _parser
+
+ return await self._client.request(self._page_cls, self._options)
+
+ async def __aiter__(self) -> AsyncIterator[_T]:
+ # https://github.com/microsoft/pyright/issues/3464
+ page = cast(
+ AsyncPageT,
+ await self, # type: ignore
)
- url = httpx.URL(str(base_url))
- if url.scheme not in ("http", "https"):
- raise OpencodeError(f"OpenCode base_url must be an http(s) URL, got {str(base_url)!r}")
- # A path prefix on the base URL is kept: requests are joined relative to it.
- return url if url.raw_path.endswith(b"/") else url.copy_with(raw_path=url.raw_path + b"/")
+ async for item in page:
+ yield item
-class _BaseClient:
- """Configuration and request building shared by both clients."""
+class BaseAsyncPage(BasePage[_T], Generic[_T]):
+ _client: AsyncAPIClient = pydantic.PrivateAttr()
- base_url: httpx.URL
- username: str
- password: str | None
- timeout: Timeout
+ def _set_private_attributes(
+ self,
+ model: Type[_T],
+ client: AsyncAPIClient,
+ options: FinalRequestOptions,
+ ) -> None:
+ if (not PYDANTIC_V1) and getattr(self, "__pydantic_private__", None) is None:
+ self.__pydantic_private__ = {}
+
+ self._model = model
+ self._client = client
+ self._options = options
+
+ async def __aiter__(self) -> AsyncIterator[_T]:
+ async for page in self.iter_pages():
+ for item in page._get_page_items():
+ yield item
+
+ async def iter_pages(self: AsyncPageT) -> AsyncIterator[AsyncPageT]:
+ page = self
+ while True:
+ yield page
+ if page.has_next_page():
+ page = await page.get_next_page()
+ else:
+ return
+
+ async def get_next_page(self: AsyncPageT) -> AsyncPageT:
+ info = self.next_page_info()
+ if not info:
+ raise RuntimeError(
+ "No next page expected; please check `.has_next_page()` before calling `.get_next_page()`."
+ )
+
+ options = self._info_to_options(info)
+ return await self._client._request_api_list(self._model, page=self.__class__, options=options)
+
+
+_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient])
+_DefaultStreamT = TypeVar("_DefaultStreamT", bound=Union[Stream[Any], AsyncStream[Any]])
+
+
+class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
+ _client: _HttpxClientT
+ _version: str
+ _base_url: URL
max_retries: int
- directory: str | None
+ timeout: Union[float, Timeout, None]
+ _strict_response_validation: bool
+ _idempotency_header: str | None
+ _default_stream_cls: type[_DefaultStreamT] | None = None
def __init__(
self,
*,
- base_url: str | httpx.URL | None,
- password: str | None,
- username: str,
- directory: str | os.PathLike[str] | None,
- timeout: Timeout,
- max_retries: int,
- default_headers: Mapping[str, str] | None,
- default_query: Mapping[str, object] | None,
+ version: str,
+ base_url: str | URL,
+ _strict_response_validation: bool,
+ max_retries: int = DEFAULT_MAX_RETRIES,
+ timeout: float | Timeout | None = DEFAULT_TIMEOUT,
+ custom_headers: Mapping[str, str] | None = None,
+ custom_query: Mapping[str, object] | None = None,
) -> None:
- if max_retries < 0:
- raise ValueError("max_retries must be zero or more")
- self.base_url = _resolve_base_url(base_url)
- self.username = username
- self.password = _resolve_password(password)
- self._authorization = (
- None
- if self.password is None
- else "Basic " + base64.b64encode(f"{username}:{self.password}".encode()).decode("ascii")
- )
- self.directory = os.fspath(directory) if directory is not None else None
- self.timeout = timeout
+ self._version = version
+ self._base_url = self._enforce_trailing_slash(URL(base_url))
self.max_retries = max_retries
- self._default_headers = dict(default_headers or {})
- self._default_query = dict(default_query or {})
+ self.timeout = timeout
+ self._custom_headers = custom_headers or {}
+ self._custom_query = custom_query or {}
+ self._strict_response_validation = _strict_response_validation
+ self._idempotency_header = None
+ self._platform: Platform | None = None
+
+ if max_retries is None: # pyright: ignore[reportUnnecessaryComparison]
+ raise TypeError(
+ "max_retries cannot be None. If you want to disable retries, pass `0`; if you want unlimited retries, pass `math.inf` or a very high number; if you want the default behavior, pass `opencode.DEFAULT_MAX_RETRIES`"
+ )
- @override
- def __repr__(self) -> str:
- # Never include the password.
- return f"{type(self).__name__}(base_url={str(self.base_url)!r})"
-
- def _build_request(self, options: RequestOptions) -> httpx.Request:
- url = self.base_url.join(options.path.lstrip("/"))
- query = {**self._default_query, **options.query, **(options.extra_query or {})}
-
- headers: dict[str, str] = {"Accept": "application/json", **_platform_headers(), **self._default_headers}
- for name, value in {**options.headers, **(options.extra_headers or {})}.items():
- if isinstance(value, NotGiven):
- headers.pop(name, None)
- elif value is not None:
- headers[name] = str(value)
- if options.stream:
- headers["Accept"] = "text/event-stream"
- if self._authorization is not None and "Authorization" not in headers:
- headers["Authorization"] = self._authorization
-
- content: bytes | None = options.content
- if content is not None:
- headers.setdefault("Content-Type", "application/octet-stream")
- elif not isinstance(options.body, NotGiven) or options.extra_body:
- body: Any = to_jsonable(options.body) if not isinstance(options.body, NotGiven) else {}
- if options.extra_body:
- if not isinstance(body, dict):
- raise TypeError("extra_body can only be used with JSON object bodies")
- extra: dict[str, Any] = to_jsonable(options.extra_body)
- body = {**cast("dict[str, Any]", body), **extra}
- content = _json_bytes(body)
- headers["Content-Type"] = "application/json"
-
- timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout
- if options.stream and isinstance(options.timeout, NotGiven):
- # Event streams stay open between events, so only bound the connect phase by default.
- base = timeout if isinstance(timeout, httpx.Timeout) else httpx.Timeout(timeout)
- timeout = httpx.Timeout(connect=base.connect, read=None, write=base.write, pool=base.pool)
- return httpx.Request(
- options.method,
- url,
- params=httpx.QueryParams(cast("list[tuple[str, str | int | float | bool | None]]", encode_query(query))),
+ def _enforce_trailing_slash(self, url: URL) -> URL:
+ if url.raw_path.endswith(b"/"):
+ return url
+ return url.copy_with(raw_path=url.raw_path + b"/")
+
+ def _make_status_error_from_response(
+ self,
+ response: httpx.Response,
+ ) -> APIStatusError:
+ if response.is_closed and not response.is_stream_consumed:
+ # We can't read the response body as it has been closed
+ # before it was read. This can happen if an event hook
+ # raises a status error.
+ body = None
+ err_msg = f"Error code: {response.status_code}"
+ else:
+ err_text = response.text.strip()
+ body = err_text
+
+ try:
+ body = json.loads(err_text)
+ err_msg = f"Error code: {response.status_code} - {body}"
+ except Exception:
+ err_msg = err_text or f"Error code: {response.status_code}"
+
+ return self._make_status_error(err_msg, body=body, response=response)
+
+ def _make_status_error(
+ self,
+ err_msg: str,
+ *,
+ body: object,
+ response: httpx.Response,
+ ) -> _exceptions.APIStatusError:
+ raise NotImplementedError()
+
+ def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0) -> httpx.Headers:
+ custom_headers = options.headers or {}
+ headers_dict = _merge_mappings(self.default_headers, custom_headers)
+ self._validate_headers(headers_dict, custom_headers)
+
+ # headers are case-insensitive while dictionaries are not.
+ headers = httpx.Headers(headers_dict)
+
+ idempotency_header = self._idempotency_header
+ if idempotency_header and options.idempotency_key and idempotency_header not in headers:
+ headers[idempotency_header] = options.idempotency_key
+
+ # Don't set these headers if they were already set or removed by the caller. We check
+ # `custom_headers`, which can contain `Omit()`, instead of `headers` to account for the removal case.
+ lower_custom_headers = [header.lower() for header in custom_headers]
+ if "x-stainless-retry-count" not in lower_custom_headers:
+ headers["x-stainless-retry-count"] = str(retries_taken)
+ if "x-stainless-read-timeout" not in lower_custom_headers:
+ timeout = self.timeout if isinstance(options.timeout, NotGiven) else options.timeout
+ if isinstance(timeout, Timeout):
+ timeout = timeout.read
+ if timeout is not None:
+ headers["x-stainless-read-timeout"] = str(timeout)
+
+ return headers
+
+ def _prepare_url(self, url: str) -> URL:
+ """
+ Merge a URL argument together with any 'base_url' on the client,
+ to create the URL used for the outgoing request.
+ """
+ # Copied from httpx's `_merge_url` method.
+ merge_url = URL(url)
+ if merge_url.is_relative_url:
+ merge_raw_path = self.base_url.raw_path + merge_url.raw_path.lstrip(b"/")
+ return self.base_url.copy_with(raw_path=merge_raw_path)
+
+ return merge_url
+
+ def _make_sse_decoder(self) -> SSEDecoder | SSEBytesDecoder:
+ return SSEDecoder()
+
+ def _build_request(
+ self,
+ options: FinalRequestOptions,
+ *,
+ retries_taken: int = 0,
+ ) -> httpx.Request:
+ if log.isEnabledFor(logging.DEBUG):
+ log.debug(
+ "Request options: %s",
+ model_dump(
+ options,
+ exclude_unset=True,
+ # Pydantic v1 can't dump every type we support in content, so we exclude it for now.
+ exclude={
+ "content",
+ }
+ if PYDANTIC_V1
+ else {},
+ ),
+ )
+ kwargs: dict[str, Any] = {}
+
+ json_data = options.json_data
+ if options.extra_json is not None:
+ if json_data is None:
+ json_data = cast(Body, options.extra_json)
+ elif is_mapping(json_data):
+ json_data = _merge_mappings(json_data, options.extra_json)
+ else:
+ raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`")
+
+ headers = self._build_headers(options, retries_taken=retries_taken)
+ params = _merge_mappings(self.default_query, options.params)
+ content_type = headers.get("Content-Type")
+ files = options.files
+
+ # If the given Content-Type header is multipart/form-data then it
+ # has to be removed so that httpx can generate the header with
+ # additional information for us as it has to be in this form
+ # for the server to be able to correctly parse the request:
+ # multipart/form-data; boundary=---abc--
+ if content_type is not None and content_type.startswith("multipart/form-data"):
+ if "boundary" not in content_type:
+ # only remove the header if the boundary hasn't been explicitly set
+ # as the caller doesn't want httpx to come up with their own boundary
+ headers.pop("Content-Type")
+
+ # As we are now sending multipart/form-data instead of application/json
+ # we need to tell httpx to use it, https://www.python-httpx.org/advanced/clients/#multipart-file-encoding
+ if json_data:
+ if not is_dict(json_data):
+ raise TypeError(
+ f"Expected query input to be a dictionary for multipart requests but got {type(json_data)} instead."
+ )
+ kwargs["data"] = self._serialize_multipartform(json_data)
+
+ # httpx determines whether or not to send a "multipart/form-data"
+ # request based on the truthiness of the "files" argument.
+ # This gets around that issue by generating a dict value that
+ # evaluates to true.
+ #
+ # https://github.com/encode/httpx/discussions/2399#discussioncomment-3814186
+ if not files:
+ files = cast(HttpxRequestFiles, ForceMultipartDict())
+
+ prepared_url = self._prepare_url(options.url)
+ # preserve hard-coded query params from the url
+ if params and prepared_url.query:
+ params = {**dict(prepared_url.params.items()), **params}
+ prepared_url = prepared_url.copy_with(raw_path=prepared_url.raw_path.split(b"?", 1)[0])
+ if "_" in prepared_url.host:
+ # work around https://github.com/encode/httpx/discussions/2880
+ kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")}
+
+ is_body_allowed = options.method.lower() != "get"
+
+ if is_body_allowed:
+ if options.content is not None and json_data is not None:
+ raise TypeError("Passing both `content` and `json_data` is not supported")
+ if options.content is not None and files is not None:
+ raise TypeError("Passing both `content` and `files` is not supported")
+ if options.content is not None:
+ kwargs["content"] = options.content
+ elif isinstance(json_data, bytes):
+ kwargs["content"] = json_data
+ elif not files:
+ # Don't set content when JSON is sent as multipart/form-data,
+ # since httpx's content param overrides other body arguments
+ kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None
+ kwargs["files"] = files
+ else:
+ headers.pop("Content-Type", None)
+ kwargs.pop("data", None)
+
+ # TODO: report this error to httpx
+ return self._client.build_request( # pyright: ignore[reportUnknownMemberType]
headers=headers,
- content=content,
- extensions={"timeout": _timeout_dict(timeout)},
+ timeout=self.timeout if isinstance(options.timeout, NotGiven) else options.timeout,
+ method=options.method,
+ url=prepared_url,
+ # the `Query` type that we use is incompatible with qs'
+ # `Params` type as it needs to be typed as `Mapping[str, object]`
+ # so that passing a `TypedDict` doesn't cause an error.
+ # https://github.com/microsoft/pyright/issues/3526#event-6715453066
+ params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None,
+ **kwargs,
)
- def _status_error(self, response: httpx.Response, raw: object) -> APIStatusError:
- body: object = raw
- fields = cast("dict[str, Any]", raw) if isinstance(raw, dict) else {}
- tag: object = fields.get("_tag")
- cls = status_error_class(response.status_code, tag if isinstance(tag, str) else None, TAGGED_ERRORS)
- return cls(error_message(body, response.status_code), response=response, body=body)
-
- def _should_retry_status(self, options: RequestOptions, response: httpx.Response) -> bool:
- statuses = constants.RETRYABLE_STATUSES if options.idempotent else constants.RETRYABLE_STATUSES_UNSAFE
- return response.status_code in statuses
-
- @staticmethod
- def _should_retry_error(options: RequestOptions, error: httpx.TransportError) -> bool:
- # These fail before the request reaches the server, so retrying never repeats work.
- if isinstance(error, (httpx.ConnectError, httpx.ConnectTimeout, httpx.PoolTimeout)):
- return True
- return options.idempotent
-
- @staticmethod
- def _retry_delay(attempt: int, response: httpx.Response | None) -> float:
- if response is not None:
- retry_after = _parse_retry_after(response.headers)
- if retry_after is not None and 0 <= retry_after <= constants.MAX_RETRY_AFTER:
- return retry_after
- delay: float = min(constants.INITIAL_RETRY_DELAY * 2.0**attempt, constants.MAX_RETRY_DELAY)
- return delay * (1 - 0.25 * random.random())
-
- @staticmethod
- def _decode_error_body(response: httpx.Response) -> object:
- if not response.content:
- return None
+ def _serialize_multipartform(self, data: Mapping[object, object]) -> dict[str, object]:
+ items = self.qs.stringify_items(
+ # TODO: type ignore is required as stringify_items is well typed but we can't be
+ # well typed without heavy validation.
+ data, # type: ignore
+ array_format="brackets",
+ )
+ serialized: dict[str, object] = {}
+ for key, value in items:
+ existing = serialized.get(key)
+
+ if not existing:
+ serialized[key] = value
+ continue
+
+ # If a value has already been set for this key then that
+ # means we're sending data like `array[]=[1, 2, 3]` and we
+ # need to tell httpx that we want to send multiple values with
+ # the same key which is done by using a list or a tuple.
+ #
+ # Note: 2d arrays should never result in the same key at both
+ # levels so it's safe to assume that if the value is a list,
+ # it was because we changed it to be a list.
+ if is_list(existing):
+ existing.append(value)
+ else:
+ serialized[key] = [existing, value]
+
+ return serialized
+
+ def _maybe_override_cast_to(self, cast_to: type[ResponseT], options: FinalRequestOptions) -> type[ResponseT]:
+ if not is_given(options.headers):
+ return cast_to
+
+ # make a copy of the headers so we don't mutate user-input
+ headers = dict(options.headers)
+
+ # we internally support defining a temporary header to override the
+ # default `cast_to` type for use with `.with_raw_response` and `.with_streaming_response`
+ # see _response.py for implementation details
+ override_cast_to = headers.pop(OVERRIDE_CAST_TO_HEADER, not_given)
+ if is_given(override_cast_to):
+ options.headers = headers
+ return cast(Type[ResponseT], override_cast_to)
+
+ return cast_to
+
+ def _should_stream_response_body(self, request: httpx.Request) -> bool:
+ return request.headers.get(RAW_RESPONSE_HEADER) == "stream" # type: ignore[no-any-return]
+
+ def _process_response_data(
+ self,
+ *,
+ data: object,
+ cast_to: type[ResponseT],
+ response: httpx.Response,
+ ) -> ResponseT:
+ if data is None:
+ return cast(ResponseT, None)
+
+ if cast_to is object:
+ return cast(ResponseT, data)
+
try:
- return response.json()
- except ValueError:
- return response.text
+ if inspect.isclass(cast_to) and issubclass(cast_to, ModelBuilderProtocol):
+ return cast(ResponseT, cast_to.build(response=response, data=data))
+
+ if self._strict_response_validation:
+ return cast(ResponseT, validate_type(type_=cast_to, value=data))
+
+ return cast(ResponseT, construct_type(type_=cast_to, value=data))
+ except pydantic.ValidationError as err:
+ raise APIResponseValidationError(response=response, body=data) from err
- def _parse(self, response: httpx.Response, kind: ResponseKind, response_type: Any, unwrap: bool) -> Any:
- if kind == "void":
+ @property
+ def qs(self) -> Querystring:
+ return Querystring()
+
+ @property
+ def custom_auth(self) -> httpx.Auth | None:
+ return None
+
+ @property
+ def auth_headers(self) -> dict[str, str]:
+ return {}
+
+ @property
+ def default_headers(self) -> dict[str, str | Omit]:
+ return {
+ "Accept": "application/json",
+ "Content-Type": "application/json",
+ "User-Agent": self.user_agent,
+ **self.platform_headers(),
+ **self.auth_headers,
+ **self._custom_headers,
+ }
+
+ @property
+ def default_query(self) -> dict[str, object]:
+ return {
+ **self._custom_query,
+ }
+
+ def _validate_headers(
+ self,
+ headers: Headers, # noqa: ARG002
+ custom_headers: Headers, # noqa: ARG002
+ ) -> None:
+ """Validate the given default headers and custom headers.
+
+ Does nothing by default.
+ """
+ return
+
+ @property
+ def user_agent(self) -> str:
+ return f"{self.__class__.__name__}/Python {self._version}"
+
+ @property
+ def base_url(self) -> URL:
+ return self._base_url
+
+ @base_url.setter
+ def base_url(self, url: URL | str) -> None:
+ self._base_url = self._enforce_trailing_slash(url if isinstance(url, URL) else URL(url))
+
+ def platform_headers(self) -> Dict[str, str]:
+ # the actual implementation is in a separate `lru_cache` decorated
+ # function because adding `lru_cache` to methods will leak memory
+ # https://github.com/python/cpython/issues/88476
+ return platform_headers(self._version, platform=self._platform)
+
+ def _parse_retry_after_header(self, response_headers: Optional[httpx.Headers] = None) -> float | None:
+ """Returns a float of the number of seconds (not milliseconds) to wait after retrying, or None if unspecified.
+
+ About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
+ See also https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After#syntax
+ """
+ if response_headers is None:
return None
- if kind == "binary":
- return response.content
+
+ # First, try the non-standard `retry-after-ms` header for milliseconds,
+ # which is more precise than integer-seconds `retry-after`
try:
- payload: Any = response.json()
- except ValueError as exc:
- raise APIResponseValidationError(response, response.text, message="Response is not valid JSON") from exc
- if unwrap:
- if not isinstance(payload, dict) or "data" not in payload:
- raise APIResponseValidationError(
- response, cast("object", payload), message="Response has no `data` field"
- )
- payload = cast("dict[str, Any]", payload)["data"]
+ retry_ms_header = response_headers.get("retry-after-ms", None)
+ return float(retry_ms_header) / 1000
+ except (TypeError, ValueError):
+ pass
+
+ # Next, try parsing `retry-after` header as seconds (allowing nonstandard floats).
+ retry_header = response_headers.get("retry-after")
try:
- return type_adapter(response_type).validate_python(payload)
- except pydantic.ValidationError as exc:
- raise APIResponseValidationError(response, payload, message=validation_summary(exc)) from exc
+ # note: the spec indicates that this should only ever be an integer
+ # but if someone sends a float there's no reason for us to not respect it
+ return float(retry_header)
+ except (TypeError, ValueError):
+ pass
+ # Last, try parsing `retry-after` as a date.
+ retry_date_tuple = email.utils.parsedate_tz(retry_header)
+ if retry_date_tuple is None:
+ return None
+
+ retry_date = email.utils.mktime_tz(retry_date_tuple)
+ return float(retry_date - time.time())
-def _json_bytes(body: object) -> bytes:
- return json.dumps(body, ensure_ascii=False, separators=(",", ":"), allow_nan=False).encode("utf-8")
+ def _calculate_retry_timeout(
+ self,
+ remaining_retries: int,
+ options: FinalRequestOptions,
+ response_headers: Optional[httpx.Headers] = None,
+ ) -> float:
+ max_retries = options.get_max_retries(self.max_retries)
+
+ # If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says.
+ retry_after = self._parse_retry_after_header(response_headers)
+ if retry_after is not None and 0 < retry_after <= 60:
+ return retry_after
+
+ # Also cap retry count to 1000 to avoid any potential overflows with `pow`
+ nb_retries = min(max_retries - remaining_retries, 1000)
+
+ # Apply exponential backoff, but not more than the max.
+ sleep_seconds = min(INITIAL_RETRY_DELAY * pow(2.0, nb_retries), MAX_RETRY_DELAY)
+
+ # Apply some jitter, plus-or-minus half a second.
+ jitter = 1 - 0.25 * random()
+ timeout = sleep_seconds * jitter
+ return timeout if timeout >= 0 else 0
+
+ def _should_retry(self, response: httpx.Response) -> bool:
+ # Note: this is not a standard header
+ should_retry_header = response.headers.get("x-should-retry")
+
+ # If the server explicitly says whether or not to retry, obey.
+ if should_retry_header == "true":
+ log.debug("Retrying as header `x-should-retry` is set to `true`")
+ return True
+ if should_retry_header == "false":
+ log.debug("Not retrying as header `x-should-retry` is set to `false`")
+ return False
+ # Retry on request timeouts.
+ if response.status_code == 408:
+ log.debug("Retrying due to status code %i", response.status_code)
+ return True
-def _timeout_dict(timeout: Timeout) -> dict[str, float | None]:
- resolved = timeout if isinstance(timeout, httpx.Timeout) else httpx.Timeout(timeout)
- return resolved.as_dict()
+ # Retry on lock timeouts.
+ if response.status_code == 409:
+ log.debug("Retrying due to status code %i", response.status_code)
+ return True
+ # Retry on rate limits.
+ if response.status_code == 429:
+ log.debug("Retrying due to status code %i", response.status_code)
+ return True
+
+ # Retry internal errors.
+ if response.status_code >= 500:
+ log.debug("Retrying due to status code %i", response.status_code)
+ return True
+
+ log.debug("Not retrying")
+ return False
+
+ def _idempotency_key(self) -> str:
+ return f"stainless-python-retry-{uuid.uuid4()}"
+
+
+class _DefaultHttpxClient(httpx.Client):
+ def __init__(self, **kwargs: Any) -> None:
+ kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
+ kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
+ kwargs.setdefault("follow_redirects", True)
+ super().__init__(**kwargs)
+
+
+if TYPE_CHECKING:
+ DefaultHttpxClient = httpx.Client
+ """An alias to `httpx.Client` that provides the same defaults that this SDK
+ uses internally.
+
+ This is useful because overriding the `http_client` with your own instance of
+ `httpx.Client` will result in httpx's defaults being used, not ours.
+ """
+else:
+ DefaultHttpxClient = _DefaultHttpxClient
+
+
+class SyncHttpxClientWrapper(DefaultHttpxClient):
+ def __del__(self) -> None:
+ if self.is_closed:
+ return
-def _parse_retry_after(headers: httpx.Headers) -> float | None:
- milliseconds = headers.get("retry-after-ms")
- if milliseconds is not None:
try:
- return float(milliseconds) / 1000
- except ValueError:
+ self.close()
+ except Exception:
pass
- value = headers.get("retry-after")
- if value is None:
- return None
- try:
- return float(value)
- except ValueError:
- parsed = email.utils.parsedate_to_datetime(value) if value else None
- if parsed is None:
- return None
- return max(parsed.timestamp() - time.time(), 0.0)
-class SyncAPIClient(_BaseClient):
- """Synchronous transport. Thread-safe: share one client across threads."""
-
- _http: httpx.Client
+class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
+ _client: httpx.Client
+ _default_stream_cls: type[Stream[Any]] | None = None
def __init__(
self,
*,
- base_url: str | httpx.URL | None,
- password: str | None,
- username: str,
- directory: str | os.PathLike[str] | None,
- timeout: Timeout,
- max_retries: int,
- default_headers: Mapping[str, str] | None,
- default_query: Mapping[str, object] | None,
- http_client: httpx.Client | None,
- transport: httpx.BaseTransport | None = None,
+ version: str,
+ base_url: str | URL,
+ max_retries: int = DEFAULT_MAX_RETRIES,
+ timeout: float | Timeout | None | NotGiven = not_given,
+ http_client: httpx.Client | None = None,
+ custom_headers: Mapping[str, str] | None = None,
+ custom_query: Mapping[str, object] | None = None,
+ _strict_response_validation: bool,
) -> None:
+ if not is_given(timeout):
+ # if the user passed in a custom http client with a non-default
+ # timeout set then we use that timeout.
+ #
+ # note: there is an edge case here where the user passes in a client
+ # where they've explicitly set the timeout to match the default timeout
+ # as this check is structural, meaning that we'll think they didn't
+ # pass in a timeout and will ignore it
+ if http_client and http_client.timeout != HTTPX_DEFAULT_TIMEOUT:
+ timeout = http_client.timeout
+ else:
+ timeout = DEFAULT_TIMEOUT
+
+ if http_client is not None and not isinstance(http_client, httpx.Client): # pyright: ignore[reportUnnecessaryIsInstance]
+ raise TypeError(
+ f"Invalid `http_client` argument; Expected an instance of `httpx.Client` but got {type(http_client)}"
+ )
+
super().__init__(
+ version=version,
+ # cast to a valid type because mypy doesn't understand our type narrowing
+ timeout=cast(Timeout, timeout),
base_url=base_url,
- password=password,
- username=username,
- directory=directory,
- timeout=timeout,
max_retries=max_retries,
- default_headers=default_headers,
- default_query=default_query,
+ custom_query=custom_query,
+ custom_headers=custom_headers,
+ _strict_response_validation=_strict_response_validation,
)
- self._owns_http = http_client is None
- if http_client is not None and transport is not None:
- raise ValueError("Pass either http_client or transport, not both")
- self._http = http_client or httpx.Client(
- limits=constants.DEFAULT_CONNECTION_LIMITS, follow_redirects=True, transport=transport
+ self._client = http_client or SyncHttpxClientWrapper(
+ base_url=base_url,
+ # cast to a valid type because mypy doesn't understand our type narrowing
+ timeout=cast(Timeout, timeout),
)
+ def is_closed(self) -> bool:
+ return self._client.is_closed
+
def close(self) -> None:
- """Closes the connection pool, unless it was passed in by the caller."""
- if self._owns_http:
- self._http.close()
+ """Close the underlying HTTPX client.
- @property
- def is_closed(self) -> bool:
- return self._http.is_closed
+ The client will *not* be usable after this.
+ """
+ # If an error is thrown while constructing a client, self._client
+ # may not be present
+ if hasattr(self, "_client"):
+ self._client.close()
- def __enter__(self) -> Self:
+ def __enter__(self: _T) -> _T:
return self
def __exit__(
- self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None
+ self,
+ exc_type: type[BaseException] | None,
+ exc: BaseException | None,
+ exc_tb: TracebackType | None,
) -> None:
self.close()
- def _send(self, options: RequestOptions, *, stream_body: bool = False) -> httpx.Response:
- attempt = 0
- while True:
- request = self._build_request(options)
- log.debug("Request %s %s (attempt %d)", request.method, request.url, attempt + 1)
+ def _prepare_options(
+ self,
+ options: FinalRequestOptions, # noqa: ARG002
+ ) -> FinalRequestOptions:
+ """Hook for mutating the given options"""
+ return options
+
+ def _prepare_request(
+ self,
+ request: httpx.Request, # noqa: ARG002
+ ) -> None:
+ """This method is used as a callback for mutating the `Request` object
+ after it has been constructed.
+ This is useful for cases where you want to add certain headers based off of
+ the request properties, e.g. `url`, `method` etc.
+ """
+ return None
+
+ @overload
+ def request(
+ self,
+ cast_to: Type[ResponseT],
+ options: FinalRequestOptions,
+ *,
+ stream: Literal[True],
+ stream_cls: Type[_StreamT],
+ ) -> _StreamT: ...
+
+ @overload
+ def request(
+ self,
+ cast_to: Type[ResponseT],
+ options: FinalRequestOptions,
+ *,
+ stream: Literal[False] = False,
+ ) -> ResponseT: ...
+
+ @overload
+ def request(
+ self,
+ cast_to: Type[ResponseT],
+ options: FinalRequestOptions,
+ *,
+ stream: bool = False,
+ stream_cls: Type[_StreamT] | None = None,
+ ) -> ResponseT | _StreamT: ...
+
+ def request(
+ self,
+ cast_to: Type[ResponseT],
+ options: FinalRequestOptions,
+ *,
+ stream: bool = False,
+ stream_cls: type[_StreamT] | None = None,
+ ) -> ResponseT | _StreamT:
+ cast_to = self._maybe_override_cast_to(cast_to, options)
+
+ # create a copy of the options we were given so that if the
+ # options are mutated later & we then retry, the retries are
+ # given the original options
+ input_options = model_copy(options)
+ if input_options.idempotency_key is None and input_options.method.lower() != "get":
+ # ensure the idempotency key is reused between requests
+ input_options.idempotency_key = self._idempotency_key()
+
+ response: httpx.Response | None = None
+ max_retries = input_options.get_max_retries(self.max_retries)
+
+ retries_taken = 0
+ for retries_taken in range(max_retries + 1):
+ options = model_copy(input_options)
+ options = self._prepare_options(options)
+
+ remaining_retries = max_retries - retries_taken
+ request = self._build_request(options, retries_taken=retries_taken)
+ self._prepare_request(request)
+
+ kwargs: HttpxSendArgs = {}
+ if self.custom_auth is not None:
+ kwargs["auth"] = self.custom_auth
+
+ if options.follow_redirects is not None:
+ kwargs["follow_redirects"] = options.follow_redirects
+
+ log.debug("Sending HTTP Request: %s %s", request.method, request.url)
+
+ response = None
try:
- response = self._http.send(request, stream=options.stream or stream_body)
- except httpx.TransportError as exc:
- if attempt < self.max_retries and self._should_retry_error(options, exc):
- delay = self._retry_delay(attempt, None)
- log.info("Retrying %s %s in %.2fs after %s", request.method, request.url, delay, type(exc).__name__)
- time.sleep(delay)
- attempt += 1
- continue
- if isinstance(exc, httpx.TimeoutException):
- raise APITimeoutError(request) from exc
- raise APIConnectionError(request=request, message=f"Connection error: {exc}") from exc
-
- log.debug("Response %s %s -> %d", request.method, request.url, response.status_code)
- if response.is_success:
- return response
- if attempt < self.max_retries and self._should_retry_status(options, response):
- delay = self._retry_delay(attempt, response)
- response.close()
- log.info(
- "Retrying %s %s in %.2fs after status %d", request.method, request.url, delay, response.status_code
+ response = self._client.send(
+ request,
+ stream=stream or self._should_stream_response_body(request=request),
+ **kwargs,
)
- time.sleep(delay)
- attempt += 1
- continue
- if options.stream or stream_body:
- response.read()
- raise self._status_error(response, self._decode_error_body(response))
+ except httpx.TimeoutException as err:
+ log.debug("Encountered httpx.TimeoutException", exc_info=True)
+
+ if remaining_retries > 0:
+ self._sleep_for_retry(
+ retries_taken=retries_taken,
+ max_retries=max_retries,
+ options=input_options,
+ response=None,
+ )
+ continue
- def request(
+ log.debug("Raising timeout error")
+ raise APITimeoutError(request=request) from err
+ except Exception as err:
+ log.debug("Encountered Exception", exc_info=True)
+
+ if remaining_retries > 0:
+ self._sleep_for_retry(
+ retries_taken=retries_taken,
+ max_retries=max_retries,
+ options=input_options,
+ response=None,
+ )
+ continue
+
+ log.debug("Raising connection error")
+ raise APIConnectionError(request=request) from err
+
+ log.debug(
+ 'HTTP Response: %s %s "%i %s" %s',
+ request.method,
+ request.url,
+ response.status_code,
+ response.reason_phrase,
+ response.headers,
+ )
+
+ try:
+ response.raise_for_status()
+ except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code
+ log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
+
+ if remaining_retries > 0 and self._should_retry(err.response):
+ err.response.close()
+ self._sleep_for_retry(
+ retries_taken=retries_taken,
+ max_retries=max_retries,
+ options=input_options,
+ response=response,
+ )
+ continue
+
+ # If the response is streamed then we need to explicitly read the response
+ # to completion before attempting to access the response text.
+ if not err.response.is_closed:
+ err.response.read()
+
+ log.debug("Re-raising status error")
+ raise self._make_status_error_from_response(err.response) from None
+
+ break
+
+ assert response is not None, "could not resolve response (should never happen)"
+ return self._process_response(
+ cast_to=cast_to,
+ options=options,
+ response=response,
+ stream=stream,
+ stream_cls=stream_cls,
+ retries_taken=retries_taken,
+ )
+
+ def _sleep_for_retry(
+ self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None
+ ) -> None:
+ remaining_retries = max_retries - retries_taken
+ if remaining_retries == 1:
+ log.debug("1 retry left")
+ else:
+ log.debug("%i retries left", remaining_retries)
+
+ timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None)
+ log.info("Retrying request to %s in %f seconds", options.url, timeout)
+
+ time.sleep(timeout)
+
+ def _process_response(
+ self,
+ *,
+ cast_to: Type[ResponseT],
+ options: FinalRequestOptions,
+ response: httpx.Response,
+ stream: bool,
+ stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None,
+ retries_taken: int = 0,
+ ) -> ResponseT:
+ origin = get_origin(cast_to) or cast_to
+
+ if (
+ inspect.isclass(origin)
+ and issubclass(origin, BaseAPIResponse)
+ # we only want to actually return the custom BaseAPIResponse class if we're
+ # returning the raw response, or if we're not streaming SSE, as if we're streaming
+ # SSE then `cast_to` doesn't actively reflect the type we need to parse into
+ and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER)))
+ ):
+ if not issubclass(origin, APIResponse):
+ raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}")
+
+ response_cls = cast("type[BaseAPIResponse[Any]]", cast_to)
+ return cast(
+ ResponseT,
+ response_cls(
+ raw=response,
+ client=self,
+ cast_to=extract_response_type(response_cls),
+ stream=stream,
+ stream_cls=stream_cls,
+ options=options,
+ retries_taken=retries_taken,
+ ),
+ )
+
+ if cast_to == httpx.Response:
+ return cast(ResponseT, response)
+
+ api_response = APIResponse(
+ raw=response,
+ client=self,
+ cast_to=cast("type[ResponseT]", cast_to), # pyright: ignore[reportUnnecessaryCast]
+ stream=stream,
+ stream_cls=stream_cls,
+ options=options,
+ retries_taken=retries_taken,
+ )
+ if bool(response.request.headers.get(RAW_RESPONSE_HEADER)):
+ return cast(ResponseT, api_response)
+
+ return api_response.parse()
+
+ def _request_api_list(
self,
- options: RequestOptions,
- *,
- kind: ResponseKind = "json",
- response_type: Any = object,
- unwrap: bool = False,
- ) -> Any:
- """Sends a request and returns the decoded response."""
- if raw_mode() is not None:
- return self._raw(options, lambda response: self._parse(response, kind, response_type, unwrap))
- response = self._send(options)
- return self._parse(response, kind, response_type, unwrap)
-
- def _raw(self, options: RequestOptions, parser: Callable[[httpx.Response], Any]) -> APIResponse[Any]:
- response = self._send(options, stream_body=raw_mode() == "stream")
- return APIResponse(response, parser, event_stream=options.stream)
-
- def stream(self, options: RequestOptions, *, item_type: type[_T] | Any) -> Stream[Any]:
- """Opens a server-sent event stream."""
- options.stream = True
- if raw_mode() is not None:
- return cast("Stream[Any]", self._raw(options, lambda response: self._open_stream(response, item_type)))
- return self._open_stream(self._send(options), item_type)
-
- def _open_stream(self, response: httpx.Response, item_type: Any) -> Stream[Any]:
- content_type = response.headers.get("content-type", "")
- if content_type.split(";", 1)[0].strip().lower() != "text/event-stream":
- response.read()
- response.close()
- raise APIResponseValidationError(
- response, response.text, message=f"Expected an event stream, got {content_type!r}"
+ model: Type[object],
+ page: Type[SyncPageT],
+ options: FinalRequestOptions,
+ ) -> SyncPageT:
+ def _parser(resp: SyncPageT) -> SyncPageT:
+ resp._set_private_attributes(
+ client=self,
+ model=model,
+ options=options,
)
- return Stream(response, type_adapter(item_type))
+ return resp
+
+ options.post_parser = _parser
+ return self.request(page, options, stream=False)
+
+ @overload
def get(
self,
path: str,
*,
- cast_to: Any = object,
- query: Query | None = None,
- headers: Headers | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> Any:
- """Sends ``GET path`` (relative to ``base_url``) with the client's auth, retries and error mapping.
-
- For endpoints this client does not cover yet. The JSON response is validated against ``cast_to``.
- """
- options = RequestOptions(method="GET", path=path, extra_query=query, extra_headers=headers, timeout=timeout)
- return self.request(options, response_type=cast_to)
+ cast_to: Type[ResponseT],
+ options: RequestOptions = {},
+ stream: Literal[False] = False,
+ ) -> ResponseT: ...
- def post(
+ @overload
+ def get(
self,
path: str,
*,
- cast_to: Any = object,
- body: object | NotGiven = NOT_GIVEN,
- query: Query | None = None,
- headers: Headers | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> Any:
- """Sends ``POST path`` (relative to ``base_url``) with the client's auth, retries and error mapping.
+ cast_to: Type[ResponseT],
+ options: RequestOptions = {},
+ stream: Literal[True],
+ stream_cls: type[_StreamT],
+ ) -> _StreamT: ...
- For endpoints this client does not cover yet. The JSON response is validated against ``cast_to``.
- """
- options = RequestOptions(
- method="POST", path=path, body=body, extra_query=query, extra_headers=headers, timeout=timeout
- )
- return self.request(options, response_type=cast_to)
+ @overload
+ def get(
+ self,
+ path: str,
+ *,
+ cast_to: Type[ResponseT],
+ options: RequestOptions = {},
+ stream: bool,
+ stream_cls: type[_StreamT] | None = None,
+ ) -> ResponseT | _StreamT: ...
- def put(
+ def get(
+ self,
+ path: str,
+ *,
+ cast_to: Type[ResponseT],
+ options: RequestOptions = {},
+ stream: bool = False,
+ stream_cls: type[_StreamT] | None = None,
+ ) -> ResponseT | _StreamT:
+ opts = FinalRequestOptions.construct(method="get", url=path, **options)
+ # cast is required because mypy complains about returning Any even though
+ # it understands the type variables
+ return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
+
+ @overload
+ def post(
+ self,
+ path: str,
+ *,
+ cast_to: Type[ResponseT],
+ body: Body | None = None,
+ content: BinaryTypes | None = None,
+ options: RequestOptions = {},
+ files: RequestFiles | None = None,
+ stream: Literal[False] = False,
+ ) -> ResponseT: ...
+
+ @overload
+ def post(
self,
path: str,
*,
- cast_to: Any = object,
- body: object | NotGiven = NOT_GIVEN,
- query: Query | None = None,
- headers: Headers | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> Any:
- """Sends ``PUT path`` (relative to ``base_url``) with the client's auth, retries and error mapping.
+ cast_to: Type[ResponseT],
+ body: Body | None = None,
+ content: BinaryTypes | None = None,
+ options: RequestOptions = {},
+ files: RequestFiles | None = None,
+ stream: Literal[True],
+ stream_cls: type[_StreamT],
+ ) -> _StreamT: ...
+
+ @overload
+ def post(
+ self,
+ path: str,
+ *,
+ cast_to: Type[ResponseT],
+ body: Body | None = None,
+ content: BinaryTypes | None = None,
+ options: RequestOptions = {},
+ files: RequestFiles | None = None,
+ stream: bool,
+ stream_cls: type[_StreamT] | None = None,
+ ) -> ResponseT | _StreamT: ...
- For endpoints this client does not cover yet. The JSON response is validated against ``cast_to``.
- """
- options = RequestOptions(
- method="PUT", path=path, body=body, extra_query=query, extra_headers=headers, timeout=timeout
+ def post(
+ self,
+ path: str,
+ *,
+ cast_to: Type[ResponseT],
+ body: Body | None = None,
+ content: BinaryTypes | None = None,
+ options: RequestOptions = {},
+ files: RequestFiles | None = None,
+ stream: bool = False,
+ stream_cls: type[_StreamT] | None = None,
+ ) -> ResponseT | _StreamT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ opts = FinalRequestOptions.construct(
+ method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
- return self.request(options, response_type=cast_to)
+ return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
def patch(
self,
path: str,
*,
- cast_to: Any = object,
- body: object | NotGiven = NOT_GIVEN,
- query: Query | None = None,
- headers: Headers | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> Any:
- """Sends ``PATCH path`` (relative to ``base_url``) with the client's auth, retries and error mapping.
+ cast_to: Type[ResponseT],
+ body: Body | None = None,
+ content: BinaryTypes | None = None,
+ files: RequestFiles | None = None,
+ options: RequestOptions = {},
+ ) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ opts = FinalRequestOptions.construct(
+ method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
+ )
+ return self.request(cast_to, opts)
- For endpoints this client does not cover yet. The JSON response is validated against ``cast_to``.
- """
- options = RequestOptions(
- method="PATCH", path=path, body=body, extra_query=query, extra_headers=headers, timeout=timeout
+ def put(
+ self,
+ path: str,
+ *,
+ cast_to: Type[ResponseT],
+ body: Body | None = None,
+ content: BinaryTypes | None = None,
+ files: RequestFiles | None = None,
+ options: RequestOptions = {},
+ ) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ opts = FinalRequestOptions.construct(
+ method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options
)
- return self.request(options, response_type=cast_to)
+ return self.request(cast_to, opts)
def delete(
self,
path: str,
*,
- cast_to: Any = object,
- query: Query | None = None,
- headers: Headers | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> Any:
- """Sends ``DELETE path`` (relative to ``base_url``) with the client's auth, retries and error mapping.
+ cast_to: Type[ResponseT],
+ body: Body | None = None,
+ content: BinaryTypes | None = None,
+ options: RequestOptions = {},
+ ) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
+ return self.request(cast_to, opts)
- For endpoints this client does not cover yet. The JSON response is validated against ``cast_to``.
- """
- options = RequestOptions(method="DELETE", path=path, extra_query=query, extra_headers=headers, timeout=timeout)
- return self.request(options, response_type=cast_to)
+ def get_api_list(
+ self,
+ path: str,
+ *,
+ model: Type[object],
+ page: Type[SyncPageT],
+ body: Body | None = None,
+ options: RequestOptions = {},
+ method: str = "get",
+ ) -> SyncPageT:
+ opts = FinalRequestOptions.construct(method=method, url=path, json_data=body, **options)
+ return self._request_api_list(model, page, opts)
- def page(self, options: RequestOptions, *, response_type: Any) -> SyncCursorPage[Any]:
- """Fetches the first page of a cursor-paginated list."""
- from ._pagination import SyncCursorPage
- if raw_mode() is not None:
- return cast(
- "SyncCursorPage[Any]",
- self._raw(
- options,
- lambda response: SyncCursorPage.build(
- self, options, response_type, self._parse(response, "json", response_type, False)
- ),
- ),
- )
- return SyncCursorPage.fetch(self, options, response_type)
+class _DefaultAsyncHttpxClient(httpx.AsyncClient):
+ def __init__(self, **kwargs: Any) -> None:
+ kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
+ kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
+ kwargs.setdefault("follow_redirects", True)
+ super().__init__(**kwargs)
+
+
+try:
+ import httpx_aiohttp
+except ImportError:
+ class _DefaultAioHttpClient(httpx.AsyncClient):
+ def __init__(self, **_kwargs: Any) -> None:
+ raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra")
+else:
-class AsyncAPIClient(_BaseClient):
- """Asynchronous transport. Works with asyncio and trio."""
+ class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore
+ def __init__(self, **kwargs: Any) -> None:
+ kwargs.setdefault("timeout", DEFAULT_TIMEOUT)
+ kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS)
+ kwargs.setdefault("follow_redirects", True)
- _http: httpx.AsyncClient
+ super().__init__(**kwargs)
+
+
+if TYPE_CHECKING:
+ DefaultAsyncHttpxClient = httpx.AsyncClient
+ """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK
+ uses internally.
+
+ This is useful because overriding the `http_client` with your own instance of
+ `httpx.AsyncClient` will result in httpx's defaults being used, not ours.
+ """
+
+ DefaultAioHttpClient = httpx.AsyncClient
+ """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`."""
+else:
+ DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient
+ DefaultAioHttpClient = _DefaultAioHttpClient
+
+
+class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient):
+ def __del__(self) -> None:
+ if self.is_closed:
+ return
+
+ try:
+ # TODO(someday): support non asyncio runtimes here
+ asyncio.get_running_loop().create_task(self.aclose())
+ except Exception:
+ pass
+
+
+class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
+ _client: httpx.AsyncClient
+ _default_stream_cls: type[AsyncStream[Any]] | None = None
def __init__(
self,
*,
- base_url: str | httpx.URL | None,
- password: str | None,
- username: str,
- directory: str | os.PathLike[str] | None,
- timeout: Timeout,
- max_retries: int,
- default_headers: Mapping[str, str] | None,
- default_query: Mapping[str, object] | None,
- http_client: httpx.AsyncClient | None,
- transport: httpx.AsyncBaseTransport | None = None,
+ version: str,
+ base_url: str | URL,
+ _strict_response_validation: bool,
+ max_retries: int = DEFAULT_MAX_RETRIES,
+ timeout: float | Timeout | None | NotGiven = not_given,
+ http_client: httpx.AsyncClient | None = None,
+ custom_headers: Mapping[str, str] | None = None,
+ custom_query: Mapping[str, object] | None = None,
) -> None:
+ if not is_given(timeout):
+ # if the user passed in a custom http client with a non-default
+ # timeout set then we use that timeout.
+ #
+ # note: there is an edge case here where the user passes in a client
+ # where they've explicitly set the timeout to match the default timeout
+ # as this check is structural, meaning that we'll think they didn't
+ # pass in a timeout and will ignore it
+ if http_client and http_client.timeout != HTTPX_DEFAULT_TIMEOUT:
+ timeout = http_client.timeout
+ else:
+ timeout = DEFAULT_TIMEOUT
+
+ if http_client is not None and not isinstance(http_client, httpx.AsyncClient): # pyright: ignore[reportUnnecessaryIsInstance]
+ raise TypeError(
+ f"Invalid `http_client` argument; Expected an instance of `httpx.AsyncClient` but got {type(http_client)}"
+ )
+
super().__init__(
+ version=version,
base_url=base_url,
- password=password,
- username=username,
- directory=directory,
- timeout=timeout,
+ # cast to a valid type because mypy doesn't understand our type narrowing
+ timeout=cast(Timeout, timeout),
max_retries=max_retries,
- default_headers=default_headers,
- default_query=default_query,
+ custom_query=custom_query,
+ custom_headers=custom_headers,
+ _strict_response_validation=_strict_response_validation,
)
- self._owns_http = http_client is None
- if http_client is not None and transport is not None:
- raise ValueError("Pass either http_client or transport, not both")
- self._http = http_client or httpx.AsyncClient(
- limits=constants.DEFAULT_CONNECTION_LIMITS, follow_redirects=True, transport=transport
+ self._client = http_client or AsyncHttpxClientWrapper(
+ base_url=base_url,
+ # cast to a valid type because mypy doesn't understand our type narrowing
+ timeout=cast(Timeout, timeout),
)
+ def is_closed(self) -> bool:
+ return self._client.is_closed
+
async def close(self) -> None:
- """Closes the connection pool, unless it was passed in by the caller."""
- if self._owns_http:
- await self._http.aclose()
+ """Close the underlying HTTPX client.
- @property
- def is_closed(self) -> bool:
- return self._http.is_closed
+ The client will *not* be usable after this.
+ """
+ await self._client.aclose()
- async def __aenter__(self) -> Self:
+ async def __aenter__(self: _T) -> _T:
return self
async def __aexit__(
- self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None
+ self,
+ exc_type: type[BaseException] | None,
+ exc: BaseException | None,
+ exc_tb: TracebackType | None,
) -> None:
await self.close()
- async def _send(self, options: RequestOptions, *, stream_body: bool = False) -> httpx.Response:
- attempt = 0
- while True:
- request = self._build_request(options)
- log.debug("Request %s %s (attempt %d)", request.method, request.url, attempt + 1)
+ async def _prepare_options(
+ self,
+ options: FinalRequestOptions, # noqa: ARG002
+ ) -> FinalRequestOptions:
+ """Hook for mutating the given options"""
+ return options
+
+ async def _prepare_request(
+ self,
+ request: httpx.Request, # noqa: ARG002
+ ) -> None:
+ """This method is used as a callback for mutating the `Request` object
+ after it has been constructed.
+ This is useful for cases where you want to add certain headers based off of
+ the request properties, e.g. `url`, `method` etc.
+ """
+ return None
+
+ @overload
+ async def request(
+ self,
+ cast_to: Type[ResponseT],
+ options: FinalRequestOptions,
+ *,
+ stream: Literal[False] = False,
+ ) -> ResponseT: ...
+
+ @overload
+ async def request(
+ self,
+ cast_to: Type[ResponseT],
+ options: FinalRequestOptions,
+ *,
+ stream: Literal[True],
+ stream_cls: type[_AsyncStreamT],
+ ) -> _AsyncStreamT: ...
+
+ @overload
+ async def request(
+ self,
+ cast_to: Type[ResponseT],
+ options: FinalRequestOptions,
+ *,
+ stream: bool,
+ stream_cls: type[_AsyncStreamT] | None = None,
+ ) -> ResponseT | _AsyncStreamT: ...
+
+ async def request(
+ self,
+ cast_to: Type[ResponseT],
+ options: FinalRequestOptions,
+ *,
+ stream: bool = False,
+ stream_cls: type[_AsyncStreamT] | None = None,
+ ) -> ResponseT | _AsyncStreamT:
+ if self._platform is None:
+ # `get_platform` can make blocking IO calls so we
+ # execute it earlier while we are in an async context
+ self._platform = await asyncify(get_platform)()
+
+ cast_to = self._maybe_override_cast_to(cast_to, options)
+
+ # create a copy of the options we were given so that if the
+ # options are mutated later & we then retry, the retries are
+ # given the original options
+ input_options = model_copy(options)
+ if input_options.idempotency_key is None and input_options.method.lower() != "get":
+ # ensure the idempotency key is reused between requests
+ input_options.idempotency_key = self._idempotency_key()
+
+ response: httpx.Response | None = None
+ max_retries = input_options.get_max_retries(self.max_retries)
+
+ retries_taken = 0
+ for retries_taken in range(max_retries + 1):
+ options = model_copy(input_options)
+ options = await self._prepare_options(options)
+
+ remaining_retries = max_retries - retries_taken
+ request = self._build_request(options, retries_taken=retries_taken)
+ await self._prepare_request(request)
+
+ kwargs: HttpxSendArgs = {}
+ if self.custom_auth is not None:
+ kwargs["auth"] = self.custom_auth
+
+ if options.follow_redirects is not None:
+ kwargs["follow_redirects"] = options.follow_redirects
+
+ log.debug("Sending HTTP Request: %s %s", request.method, request.url)
+
+ response = None
try:
- response = await self._http.send(request, stream=options.stream or stream_body)
- except httpx.TransportError as exc:
- if attempt < self.max_retries and self._should_retry_error(options, exc):
- delay = self._retry_delay(attempt, None)
- log.info("Retrying %s %s in %.2fs after %s", request.method, request.url, delay, type(exc).__name__)
- await anyio.sleep(delay)
- attempt += 1
- continue
- if isinstance(exc, httpx.TimeoutException):
- raise APITimeoutError(request) from exc
- raise APIConnectionError(request=request, message=f"Connection error: {exc}") from exc
-
- log.debug("Response %s %s -> %d", request.method, request.url, response.status_code)
- if response.is_success:
- return response
- if attempt < self.max_retries and self._should_retry_status(options, response):
- delay = self._retry_delay(attempt, response)
- await response.aclose()
- log.info(
- "Retrying %s %s in %.2fs after status %d", request.method, request.url, delay, response.status_code
+ response = await self._client.send(
+ request,
+ stream=stream or self._should_stream_response_body(request=request),
+ **kwargs,
)
- await anyio.sleep(delay)
- attempt += 1
- continue
- if options.stream or stream_body:
- await response.aread()
- raise self._status_error(response, self._decode_error_body(response))
+ except httpx.TimeoutException as err:
+ log.debug("Encountered httpx.TimeoutException", exc_info=True)
+
+ if remaining_retries > 0:
+ await self._sleep_for_retry(
+ retries_taken=retries_taken,
+ max_retries=max_retries,
+ options=input_options,
+ response=None,
+ )
+ continue
- async def request(
+ log.debug("Raising timeout error")
+ raise APITimeoutError(request=request) from err
+ except Exception as err:
+ log.debug("Encountered Exception", exc_info=True)
+
+ if remaining_retries > 0:
+ await self._sleep_for_retry(
+ retries_taken=retries_taken,
+ max_retries=max_retries,
+ options=input_options,
+ response=None,
+ )
+ continue
+
+ log.debug("Raising connection error")
+ raise APIConnectionError(request=request) from err
+
+ log.debug(
+ 'HTTP Response: %s %s "%i %s" %s',
+ request.method,
+ request.url,
+ response.status_code,
+ response.reason_phrase,
+ response.headers,
+ )
+
+ try:
+ response.raise_for_status()
+ except httpx.HTTPStatusError as err: # thrown on 4xx and 5xx status code
+ log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
+
+ if remaining_retries > 0 and self._should_retry(err.response):
+ await err.response.aclose()
+ await self._sleep_for_retry(
+ retries_taken=retries_taken,
+ max_retries=max_retries,
+ options=input_options,
+ response=response,
+ )
+ continue
+
+ # If the response is streamed then we need to explicitly read the response
+ # to completion before attempting to access the response text.
+ if not err.response.is_closed:
+ await err.response.aread()
+
+ log.debug("Re-raising status error")
+ raise self._make_status_error_from_response(err.response) from None
+
+ break
+
+ assert response is not None, "could not resolve response (should never happen)"
+ return await self._process_response(
+ cast_to=cast_to,
+ options=options,
+ response=response,
+ stream=stream,
+ stream_cls=stream_cls,
+ retries_taken=retries_taken,
+ )
+
+ async def _sleep_for_retry(
+ self, *, retries_taken: int, max_retries: int, options: FinalRequestOptions, response: httpx.Response | None
+ ) -> None:
+ remaining_retries = max_retries - retries_taken
+ if remaining_retries == 1:
+ log.debug("1 retry left")
+ else:
+ log.debug("%i retries left", remaining_retries)
+
+ timeout = self._calculate_retry_timeout(remaining_retries, options, response.headers if response else None)
+ log.info("Retrying request to %s in %f seconds", options.url, timeout)
+
+ await anyio.sleep(timeout)
+
+ async def _process_response(
self,
- options: RequestOptions,
- *,
- kind: ResponseKind = "json",
- response_type: Any = object,
- unwrap: bool = False,
- ) -> Any:
- """Sends a request and returns the decoded response."""
- if raw_mode() is not None:
- return await self._raw(options, lambda response: self._parse(response, kind, response_type, unwrap))
- response = await self._send(options)
- return self._parse(response, kind, response_type, unwrap)
-
- async def _raw(self, options: RequestOptions, parser: Callable[[httpx.Response], Any]) -> AsyncAPIResponse[Any]:
- response = await self._send(options, stream_body=raw_mode() == "stream")
- return AsyncAPIResponse(response, parser, event_stream=options.stream)
-
- async def stream(self, options: RequestOptions, *, item_type: type[_T] | Any) -> AsyncStream[Any]:
- """Opens a server-sent event stream."""
- options.stream = True
- if raw_mode() is not None:
- raw = await self._raw(options, lambda response: self._open_stream(response, item_type))
- return cast("AsyncStream[Any]", raw)
- return await self._open_async_stream(await self._send(options), item_type)
-
- @staticmethod
- def _open_stream(response: httpx.Response, item_type: Any) -> AsyncStream[Any]:
- content_type = response.headers.get("content-type", "")
- if content_type.split(";", 1)[0].strip().lower() != "text/event-stream":
- raise APIResponseValidationError(response, None, message=f"Expected an event stream, got {content_type!r}")
- return AsyncStream(response, type_adapter(item_type))
-
- async def _open_async_stream(self, response: httpx.Response, item_type: Any) -> AsyncStream[Any]:
- content_type = response.headers.get("content-type", "")
- if content_type.split(";", 1)[0].strip().lower() != "text/event-stream":
- await response.aread()
- await response.aclose()
- raise APIResponseValidationError(
- response, response.text, message=f"Expected an event stream, got {content_type!r}"
+ *,
+ cast_to: Type[ResponseT],
+ options: FinalRequestOptions,
+ response: httpx.Response,
+ stream: bool,
+ stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None,
+ retries_taken: int = 0,
+ ) -> ResponseT:
+ origin = get_origin(cast_to) or cast_to
+
+ if (
+ inspect.isclass(origin)
+ and issubclass(origin, BaseAPIResponse)
+ # we only want to actually return the custom BaseAPIResponse class if we're
+ # returning the raw response, or if we're not streaming SSE, as if we're streaming
+ # SSE then `cast_to` doesn't actively reflect the type we need to parse into
+ and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER)))
+ ):
+ if not issubclass(origin, AsyncAPIResponse):
+ raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}")
+
+ response_cls = cast("type[BaseAPIResponse[Any]]", cast_to)
+ return cast(
+ "ResponseT",
+ response_cls(
+ raw=response,
+ client=self,
+ cast_to=extract_response_type(response_cls),
+ stream=stream,
+ stream_cls=stream_cls,
+ options=options,
+ retries_taken=retries_taken,
+ ),
)
- return AsyncStream(response, type_adapter(item_type))
+ if cast_to == httpx.Response:
+ return cast(ResponseT, response)
+
+ api_response = AsyncAPIResponse(
+ raw=response,
+ client=self,
+ cast_to=cast("type[ResponseT]", cast_to), # pyright: ignore[reportUnnecessaryCast]
+ stream=stream,
+ stream_cls=stream_cls,
+ options=options,
+ retries_taken=retries_taken,
+ )
+ if bool(response.request.headers.get(RAW_RESPONSE_HEADER)):
+ return cast(ResponseT, api_response)
+
+ return await api_response.parse()
+
+ def _request_api_list(
+ self,
+ model: Type[_T],
+ page: Type[AsyncPageT],
+ options: FinalRequestOptions,
+ ) -> AsyncPaginator[_T, AsyncPageT]:
+ return AsyncPaginator(client=self, options=options, page_cls=page, model=model)
+
+ @overload
async def get(
self,
path: str,
*,
- cast_to: Any = object,
- query: Query | None = None,
- headers: Headers | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> Any:
- """Sends ``GET path`` (relative to ``base_url``) with the client's auth, retries and error mapping.
-
- For endpoints this client does not cover yet. The JSON response is validated against ``cast_to``.
- """
- options = RequestOptions(method="GET", path=path, extra_query=query, extra_headers=headers, timeout=timeout)
- return await self.request(options, response_type=cast_to)
+ cast_to: Type[ResponseT],
+ options: RequestOptions = {},
+ stream: Literal[False] = False,
+ ) -> ResponseT: ...
- async def post(
+ @overload
+ async def get(
self,
path: str,
*,
- cast_to: Any = object,
- body: object | NotGiven = NOT_GIVEN,
- query: Query | None = None,
- headers: Headers | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> Any:
- """Sends ``POST path`` (relative to ``base_url``) with the client's auth, retries and error mapping.
+ cast_to: Type[ResponseT],
+ options: RequestOptions = {},
+ stream: Literal[True],
+ stream_cls: type[_AsyncStreamT],
+ ) -> _AsyncStreamT: ...
- For endpoints this client does not cover yet. The JSON response is validated against ``cast_to``.
- """
- options = RequestOptions(
- method="POST", path=path, body=body, extra_query=query, extra_headers=headers, timeout=timeout
- )
- return await self.request(options, response_type=cast_to)
+ @overload
+ async def get(
+ self,
+ path: str,
+ *,
+ cast_to: Type[ResponseT],
+ options: RequestOptions = {},
+ stream: bool,
+ stream_cls: type[_AsyncStreamT] | None = None,
+ ) -> ResponseT | _AsyncStreamT: ...
- async def put(
+ async def get(
+ self,
+ path: str,
+ *,
+ cast_to: Type[ResponseT],
+ options: RequestOptions = {},
+ stream: bool = False,
+ stream_cls: type[_AsyncStreamT] | None = None,
+ ) -> ResponseT | _AsyncStreamT:
+ opts = FinalRequestOptions.construct(method="get", url=path, **options)
+ return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
+
+ @overload
+ async def post(
+ self,
+ path: str,
+ *,
+ cast_to: Type[ResponseT],
+ body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
+ files: RequestFiles | None = None,
+ options: RequestOptions = {},
+ stream: Literal[False] = False,
+ ) -> ResponseT: ...
+
+ @overload
+ async def post(
self,
path: str,
*,
- cast_to: Any = object,
- body: object | NotGiven = NOT_GIVEN,
- query: Query | None = None,
- headers: Headers | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> Any:
- """Sends ``PUT path`` (relative to ``base_url``) with the client's auth, retries and error mapping.
+ cast_to: Type[ResponseT],
+ body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
+ files: RequestFiles | None = None,
+ options: RequestOptions = {},
+ stream: Literal[True],
+ stream_cls: type[_AsyncStreamT],
+ ) -> _AsyncStreamT: ...
+
+ @overload
+ async def post(
+ self,
+ path: str,
+ *,
+ cast_to: Type[ResponseT],
+ body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
+ files: RequestFiles | None = None,
+ options: RequestOptions = {},
+ stream: bool,
+ stream_cls: type[_AsyncStreamT] | None = None,
+ ) -> ResponseT | _AsyncStreamT: ...
- For endpoints this client does not cover yet. The JSON response is validated against ``cast_to``.
- """
- options = RequestOptions(
- method="PUT", path=path, body=body, extra_query=query, extra_headers=headers, timeout=timeout
+ async def post(
+ self,
+ path: str,
+ *,
+ cast_to: Type[ResponseT],
+ body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
+ files: RequestFiles | None = None,
+ options: RequestOptions = {},
+ stream: bool = False,
+ stream_cls: type[_AsyncStreamT] | None = None,
+ ) -> ResponseT | _AsyncStreamT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ opts = FinalRequestOptions.construct(
+ method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
)
- return await self.request(options, response_type=cast_to)
+ return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
async def patch(
self,
path: str,
*,
- cast_to: Any = object,
- body: object | NotGiven = NOT_GIVEN,
- query: Query | None = None,
- headers: Headers | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> Any:
- """Sends ``PATCH path`` (relative to ``base_url``) with the client's auth, retries and error mapping.
+ cast_to: Type[ResponseT],
+ body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
+ files: RequestFiles | None = None,
+ options: RequestOptions = {},
+ ) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ opts = FinalRequestOptions.construct(
+ method="patch",
+ url=path,
+ json_data=body,
+ content=content,
+ files=await async_to_httpx_files(files),
+ **options,
+ )
+ return await self.request(cast_to, opts)
- For endpoints this client does not cover yet. The JSON response is validated against ``cast_to``.
- """
- options = RequestOptions(
- method="PATCH", path=path, body=body, extra_query=query, extra_headers=headers, timeout=timeout
+ async def put(
+ self,
+ path: str,
+ *,
+ cast_to: Type[ResponseT],
+ body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
+ files: RequestFiles | None = None,
+ options: RequestOptions = {},
+ ) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if files is not None and content is not None:
+ raise TypeError("Passing both `files` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ opts = FinalRequestOptions.construct(
+ method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options
)
- return await self.request(options, response_type=cast_to)
+ return await self.request(cast_to, opts)
async def delete(
self,
path: str,
*,
- cast_to: Any = object,
- query: Query | None = None,
- headers: Headers | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> Any:
- """Sends ``DELETE path`` (relative to ``base_url``) with the client's auth, retries and error mapping.
-
- For endpoints this client does not cover yet. The JSON response is validated against ``cast_to``.
- """
- options = RequestOptions(method="DELETE", path=path, extra_query=query, extra_headers=headers, timeout=timeout)
- return await self.request(options, response_type=cast_to)
-
- def page(self, options: RequestOptions, *, response_type: Any) -> AsyncPaginator[Any]:
- """Returns an awaitable, async-iterable paginator over a cursor-paginated list."""
- from ._pagination import AsyncCursorPage, AsyncPaginator
-
- if raw_mode() is not None:
- # Awaited by the raw-response wrapper: the raw form of a list call is the first page's response.
- raw: Coroutine[Any, Any, AsyncAPIResponse[Any]] = self._raw(
- options,
- lambda response: AsyncCursorPage.build(
- self, options, response_type, self._parse(response, "json", response_type, False)
- ),
+ cast_to: Type[ResponseT],
+ body: Body | None = None,
+ content: AsyncBinaryTypes | None = None,
+ options: RequestOptions = {},
+ ) -> ResponseT:
+ if body is not None and content is not None:
+ raise TypeError("Passing both `body` and `content` is not supported")
+ if isinstance(body, bytes):
+ warnings.warn(
+ "Passing raw bytes as `body` is deprecated and will be removed in a future version. "
+ "Please pass raw bytes via the `content` parameter instead.",
+ DeprecationWarning,
+ stacklevel=2,
)
- return cast("AsyncPaginator[Any]", raw)
- return AsyncPaginator(self, options, response_type)
+ opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options)
+ return await self.request(cast_to, opts)
+
+ def get_api_list(
+ self,
+ path: str,
+ *,
+ model: Type[_T],
+ page: Type[AsyncPageT],
+ body: Body | None = None,
+ options: RequestOptions = {},
+ method: str = "get",
+ ) -> AsyncPaginator[_T, AsyncPageT]:
+ opts = FinalRequestOptions.construct(method=method, url=path, json_data=body, **options)
+ return self._request_api_list(model, page, opts)
+
+
+def make_request_options(
+ *,
+ query: Query | None = None,
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ idempotency_key: str | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ post_parser: PostParser | NotGiven = not_given,
+) -> RequestOptions:
+ """Create a dict of type RequestOptions without keys of NotGiven values."""
+ options: RequestOptions = {}
+ if extra_headers is not None:
+ options["headers"] = extra_headers
+
+ if extra_body is not None:
+ options["extra_json"] = cast(AnyMapping, extra_body)
+
+ if query is not None:
+ options["params"] = query
+
+ if extra_query is not None:
+ options["params"] = {**options.get("params", {}), **extra_query}
+
+ if not isinstance(timeout, NotGiven):
+ options["timeout"] = timeout
+
+ if idempotency_key is not None:
+ options["idempotency_key"] = idempotency_key
+
+ if is_given(post_parser):
+ # internal
+ options["post_parser"] = post_parser # type: ignore
+
+ return options
+
+
+class ForceMultipartDict(Dict[str, None]):
+ def __bool__(self) -> bool:
+ return True
+
+
+class OtherPlatform:
+ def __init__(self, name: str) -> None:
+ self.name = name
+
+ @override
+ def __str__(self) -> str:
+ return f"Other:{self.name}"
+
+
+Platform = Union[
+ OtherPlatform,
+ Literal[
+ "MacOS",
+ "Linux",
+ "Windows",
+ "FreeBSD",
+ "OpenBSD",
+ "iOS",
+ "Android",
+ "Unknown",
+ ],
+]
+
+
+def get_platform() -> Platform:
+ try:
+ system = platform.system().lower()
+ platform_name = platform.platform().lower()
+ except Exception:
+ return "Unknown"
+
+ if "iphone" in platform_name or "ipad" in platform_name:
+ # Tested using Python3IDE on an iPhone 11 and Pythonista on an iPad 7
+ # system is Darwin and platform_name is a string like:
+ # - Darwin-21.6.0-iPhone12,1-64bit
+ # - Darwin-21.6.0-iPad7,11-64bit
+ return "iOS"
+
+ if system == "darwin":
+ return "MacOS"
+
+ if system == "windows":
+ return "Windows"
+
+ if "android" in platform_name:
+ # Tested using Pydroid 3
+ # system is Linux and platform_name is a string like 'Linux-5.10.81-android12-9-00001-geba40aecb3b7-ab8534902-aarch64-with-libc'
+ return "Android"
+
+ if system == "linux":
+ # https://distro.readthedocs.io/en/latest/#distro.id
+ distro_id = distro.id()
+ if distro_id == "freebsd":
+ return "FreeBSD"
+
+ if distro_id == "openbsd":
+ return "OpenBSD"
+
+ return "Linux"
+
+ if platform_name:
+ return OtherPlatform(platform_name)
+
+ return "Unknown"
+
+
+@lru_cache(maxsize=None)
+def platform_headers(version: str, *, platform: Platform | None) -> Dict[str, str]:
+ return {
+ "X-Stainless-Lang": "python",
+ "X-Stainless-Package-Version": version,
+ "X-Stainless-OS": str(platform or get_platform()),
+ "X-Stainless-Arch": str(get_architecture()),
+ "X-Stainless-Runtime": get_python_runtime(),
+ "X-Stainless-Runtime-Version": get_python_version(),
+ }
+
+
+class OtherArch:
+ def __init__(self, name: str) -> None:
+ self.name = name
+
+ @override
+ def __str__(self) -> str:
+ return f"other:{self.name}"
+
+
+Arch = Union[OtherArch, Literal["x32", "x64", "arm", "arm64", "unknown"]]
+
+
+def get_python_runtime() -> str:
+ try:
+ return platform.python_implementation()
+ except Exception:
+ return "unknown"
+
+
+def get_python_version() -> str:
+ try:
+ return platform.python_version()
+ except Exception:
+ return "unknown"
+
+
+def get_architecture() -> Arch:
+ try:
+ machine = platform.machine().lower()
+ except Exception:
+ return "unknown"
+
+ if machine in ("arm64", "aarch64"):
+ return "arm64"
+
+ # TODO: untested
+ if machine == "arm":
+ return "arm"
+
+ if machine == "x86_64":
+ return "x64"
+
+ # TODO: untested
+ if sys.maxsize <= 2**32:
+ return "x32"
+
+ if machine:
+ return OtherArch(machine)
+
+ return "unknown"
+
+
+def _merge_mappings(
+ obj1: Mapping[_T_co, Union[_T, Omit]],
+ obj2: Mapping[_T_co, Union[_T, Omit]],
+) -> Dict[_T_co, _T]:
+ """Merge two mappings of the same type, removing any values that are instances of `Omit`.
+
+ In cases with duplicate keys the second mapping takes precedence.
+ """
+ merged = {**obj1, **obj2}
+ return {key: value for key, value in merged.items() if not isinstance(value, Omit)}
diff --git a/src/opencode/_client.py b/src/opencode/_client.py
index 655ad80..852070a 100644
--- a/src/opencode/_client.py
+++ b/src/opencode/_client.py
@@ -1,367 +1,1621 @@
-from __future__ import annotations
-
-from functools import cached_property
-from typing import TYPE_CHECKING, Any
-
-from typing_extensions import Self
-
-from ._base_client import AsyncAPIClient, SyncAPIClient
-from ._constants import DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, DEFAULT_USERNAME
-from ._events import AsyncEventHub, EventHub
-from ._rpc import AsyncTypedRpcResource, TypedRpcResource
-from ._service import (
- EnsureReason,
- ServiceError,
- Version,
- async_discover,
- async_ensure,
- discover as discover_service,
- ensure as ensure_service,
-)
-from ._types import NOT_GIVEN, NotGiven, Timeout
-from .resources import AsyncResources, SyncResources
-
-if TYPE_CHECKING:
- import os
- from collections.abc import Callable, Iterable, Mapping, Sequence
-
- import httpx
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
- from .testing import FakeServer
-
-__all__ = ["AsyncOpencode", "Opencode"]
+from __future__ import annotations
-FAKE_BASE_URL = "http://opencode.fake"
-"""Base URL of clients made by ``Opencode.fake()``."""
+import os
+from typing import TYPE_CHECKING, Any, Mapping
+from typing_extensions import Self, override
+import httpx
-class Opencode(SyncAPIClient, SyncResources):
- """Synchronous client for an OpenCode server.
+from . import _exceptions
+from ._qs import Querystring
+from ._types import (
+ Omit,
+ Timeout,
+ NotGiven,
+ Transport,
+ ProxiesTypes,
+ RequestOptions,
+ not_given,
+)
+from ._utils import (
+ is_given,
+ is_mapping_t,
+ get_async_library,
+)
+from ._compat import cached_property
+from ._version import __version__
+from ._streaming import Stream as Stream, AsyncStream as AsyncStream
+from ._exceptions import APIStatusError
+from ._base_client import (
+ DEFAULT_MAX_RETRIES,
+ SyncAPIClient,
+ AsyncAPIClient,
+)
- Args:
- base_url: Server URL, for example ``http://127.0.0.1:4096``. Defaults to ``OPENCODE_BASE_URL``.
- password: Server password. Defaults to ``OPENCODE_PASSWORD``, then ``OPENCODE_SERVER_PASSWORD``. Pass ``""``
- to send no credentials.
- username: Basic auth user name. The OpenCode server uses ``opencode``.
- directory: Default project directory. Sent as the ``location`` of every request that accepts one, unless
- the call passes its own ``location``.
- timeout: Default timeout in seconds, or an ``httpx.Timeout``. Event streams only apply the connect timeout.
- max_retries: Retries for connection failures and transient statuses. Requests that may have changed state
- on the server are only retried when the server refused them before acting (429 and 503).
- default_headers: Headers sent with every request.
- default_query: Query parameters sent with every request.
- http_client: A preconfigured ``httpx.Client`` (proxies, custom transports, event hooks). The caller keeps
- ownership and must close it.
- transport: An ``httpx`` transport for the client's own connection pool, such as
- :class:`opencode.testing.FakeServer`. Cannot be combined with ``http_client``.
+if TYPE_CHECKING:
+ from .resources import (
+ mcp,
+ pty,
+ rpc,
+ vcs,
+ file,
+ form,
+ agent,
+ debug,
+ event,
+ model,
+ shell,
+ skill,
+ voice,
+ config,
+ plugin,
+ server,
+ command,
+ message,
+ project,
+ session,
+ generate,
+ location,
+ provider,
+ worktree,
+ migration,
+ reference,
+ websearch,
+ credential,
+ permission,
+ integration,
+ experimental,
+ )
+ from .resources.rpc import RpcResource, AsyncRpcResource
+ from .resources.file import FileResource, AsyncFileResource
+ from .resources.form import FormResource, AsyncFormResource
+ from .resources.agent import AgentResource, AsyncAgentResource
+ from .resources.event import EventResource, AsyncEventResource
+ from .resources.model import ModelResource, AsyncModelResource
+ from .resources.shell import ShellResource, AsyncShellResource
+ from .resources.skill import SkillResource, AsyncSkillResource
+ from .resources.voice import VoiceResource, AsyncVoiceResource
+ from .resources.config import ConfigResource, AsyncConfigResource
+ from .resources.plugin import PluginResource, AsyncPluginResource
+ from .resources.server import ServerResource, AsyncServerResource
+ from .resources.command import CommandResource, AsyncCommandResource
+ from .resources.mcp.mcp import McpResource, AsyncMcpResource
+ from .resources.message import MessageResource, AsyncMessageResource
+ from .resources.project import ProjectResource, AsyncProjectResource
+ from .resources.pty.pty import PtyResource, AsyncPtyResource
+ from .resources.vcs.vcs import VcsResource, AsyncVcsResource
+ from .resources.generate import GenerateResource, AsyncGenerateResource
+ from .resources.location import LocationResource, AsyncLocationResource
+ from .resources.provider import ProviderResource, AsyncProviderResource
+ from .resources.worktree import WorktreeResource, AsyncWorktreeResource
+ from .resources.reference import ReferenceResource, AsyncReferenceResource
+ from .resources.websearch import WebsearchResource, AsyncWebsearchResource
+ from .resources.credential import CredentialResource, AsyncCredentialResource
+ from .resources.debug.debug import DebugResource, AsyncDebugResource
+ from .resources.session.session import SessionResource, AsyncSessionResource
+ from .resources.migration.migration import MigrationResource, AsyncMigrationResource
+ from .resources.permission.permission import PermissionResource, AsyncPermissionResource
+ from .resources.integration.integration import IntegrationResource, AsyncIntegrationResource
+ from .resources.experimental.experimental import ExperimentalResource, AsyncExperimentalResource
- Example::
+__all__ = [
+ "Timeout",
+ "Transport",
+ "ProxiesTypes",
+ "RequestOptions",
+ "Opencode",
+ "AsyncOpencode",
+ "Client",
+ "AsyncClient",
+]
- from opencode import Opencode
- with Opencode("http://127.0.0.1:4096", password="...") as client:
- for agent in client.agent.list():
- print(agent.id)
- """
+class Opencode(SyncAPIClient):
+ # client options
+ username: str | None
+ password: str | None
def __init__(
self,
- base_url: str | httpx.URL | None = None,
*,
+ username: str | None = None,
password: str | None = None,
- username: str = DEFAULT_USERNAME,
- directory: str | os.PathLike[str] | None = None,
- timeout: Timeout = DEFAULT_TIMEOUT,
+ base_url: str | httpx.URL | None = None,
+ timeout: float | Timeout | None | NotGiven = not_given,
max_retries: int = DEFAULT_MAX_RETRIES,
default_headers: Mapping[str, str] | None = None,
default_query: Mapping[str, object] | None = None,
+ # Configure a custom httpx client.
+ # We provide a `DefaultHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`.
+ # See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details.
http_client: httpx.Client | None = None,
- transport: httpx.BaseTransport | None = None,
+ # Enable or disable schema validation for data returned by the API.
+ # When enabled an error APIResponseValidationError is raised
+ # if the API responds with invalid data for the expected schema.
+ #
+ # This parameter may be removed or changed in the future.
+ # If you rely on this feature, please open a GitHub issue
+ # outlining your use-case to help us decide if it should be
+ # part of our public interface in the future.
+ _strict_response_validation: bool = False,
) -> None:
+ """Construct a new synchronous Opencode client instance.
+
+ This automatically infers the `password` argument from the `OPENCODE_PASSWORD` environment variable if it is not provided.
+ """
+ if username is None:
+ username = "opencode"
+ self.username = username
+
+ if password is None:
+ password = os.environ.get("OPENCODE_PASSWORD")
+ self.password = password
+
+ if base_url is None:
+ base_url = os.environ.get("OPENCODE_BASE_URL")
+ if base_url is None:
+ base_url = f"http://127.0.0.1:4096"
+
+ custom_headers_env = os.environ.get("OPENCODE_CUSTOM_HEADERS")
+ if custom_headers_env is not None:
+ parsed: dict[str, str] = {}
+ for line in custom_headers_env.split("\n"):
+ colon = line.find(":")
+ if colon >= 0:
+ parsed[line[:colon].strip()] = line[colon + 1 :].strip()
+ default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}
+
super().__init__(
+ version=__version__,
base_url=base_url,
- password=password,
- username=username,
- directory=directory,
- timeout=timeout,
max_retries=max_retries,
- default_headers=default_headers,
- default_query=default_query,
+ timeout=timeout,
http_client=http_client,
- transport=transport,
+ custom_headers=default_headers,
+ custom_query=default_query,
+ _strict_response_validation=_strict_response_validation,
)
- @classmethod
- def from_service(
- cls,
- *,
- start: bool = True,
- file: str | os.PathLike[str] | None = None,
- version: Version | None = None,
- command: Sequence[str] | None = None,
- env: Mapping[str, str] | None = None,
- on_start: Callable[[EnsureReason, str | None], None] | None = None,
- directory: str | os.PathLike[str] | None = None,
- timeout: Timeout = DEFAULT_TIMEOUT,
- max_retries: int = DEFAULT_MAX_RETRIES,
- default_headers: Mapping[str, str] | None = None,
- default_query: Mapping[str, object] | None = None,
- ) -> Self:
- """Connect to the local background service, starting ``opencode serve --service`` if needed.
-
- Uses :func:`opencode.service.ensure`, or :func:`opencode.service.discover` when ``start`` is false, and
- sends the service's password with every request.
-
- Args:
- start: Start or replace the service when no matching one is running. When false, raise instead.
- file: Registration file. Defaults to ``$XDG_STATE_HOME/opencode/service.json``.
- version: Required exact version, or a predicate on the service version.
- command: Service command and arguments, used when starting it.
- env: Variables added to the environment of a started service.
- on_start: Called once before a service process is started.
- directory: Default project directory, as in the constructor.
- timeout: Default request timeout, as in the constructor.
- max_retries: Retries for failed requests, as in the constructor.
- default_headers: Headers sent with every request.
- default_query: Query parameters sent with every request.
-
- Raises:
- ServiceError: No matching service is running and ``start`` is false, or it could not be started.
-
- Example::
-
- with Opencode.from_service() as client:
- print(client.server.info().version)
- """
- if start:
- endpoint = ensure_service(file, version, command=command, env=env, on_start=on_start)
- else:
- found = discover_service(file, version)
- if found is None:
- raise ServiceError("No matching OpenCode background service is running")
- endpoint = found
- return cls(
- endpoint.url,
- # An empty password sends no credentials instead of falling back to OPENCODE_PASSWORD.
- password="" if endpoint.auth is None else endpoint.auth.password,
- username=DEFAULT_USERNAME if endpoint.auth is None else endpoint.auth.username,
- directory=directory,
- timeout=timeout,
- max_retries=max_retries,
- default_headers=default_headers,
- default_query=default_query,
- )
+ @cached_property
+ def agent(self) -> AgentResource:
+ from .resources.agent import AgentResource
+
+ return AgentResource(self)
@cached_property
- def events(self) -> EventHub:
- """The shared event stream: one connection for every subscriber. See :class:`opencode.EventHub`."""
- return EventHub(self)
+ def command(self) -> CommandResource:
+ from .resources.command import CommandResource
+
+ return CommandResource(self)
@cached_property
- def rpc(self) -> TypedRpcResource:
- """Plugin RPC calls: ``client.rpc.call(...)``, or ``client.rpc(Definition)`` for typed access."""
- return TypedRpcResource(self)
+ def config(self) -> ConfigResource:
+ from .resources.config import ConfigResource
- @classmethod
- def fake(cls, server: FakeServer | None = None, **kwargs: Any) -> Self:
- """Returns a client wired to an in-process :class:`opencode.testing.FakeServer`, for tests.
+ return ConfigResource(self)
- Pass your own ``server`` to configure replies and inspect requests; other arguments go to the constructor.
- """
- from .testing import FakeServer
+ @cached_property
+ def credential(self) -> CredentialResource:
+ from .resources.credential import CredentialResource
- kwargs.setdefault("password", "")
- return cls(FAKE_BASE_URL, transport=server if server is not None else FakeServer(), **kwargs)
+ return CredentialResource(self)
- def wait_until_ready(
- self, *, agents: Iterable[str] = (), mcp_servers: Iterable[str] = (), timeout: float = 60
- ) -> None:
- """Blocks until the server answers, the named ``agents`` are listed and the named ``mcp_servers`` are
- connected.
+ @cached_property
+ def debug(self) -> DebugResource:
+ from .resources.debug import DebugResource
- Agents load asynchronously after the server starts, so call this before creating sessions on a freshly
- booted server. Connection errors and 503s while the server boots count as not ready yet.
+ return DebugResource(self)
- Raises:
- TimeoutError: Something was still missing after ``timeout`` seconds. The message names it.
- """
- from .lib._ready import wait_until_ready
+ @cached_property
+ def event(self) -> EventResource:
+ from .resources.event import EventResource
+
+ return EventResource(self)
+
+ @cached_property
+ def experimental(self) -> ExperimentalResource:
+ from .resources.experimental import ExperimentalResource
+
+ return ExperimentalResource(self)
+
+ @cached_property
+ def file(self) -> FileResource:
+ from .resources.file import FileResource
+
+ return FileResource(self)
+
+ @cached_property
+ def form(self) -> FormResource:
+ from .resources.form import FormResource
+
+ return FormResource(self)
+
+ @cached_property
+ def generate(self) -> GenerateResource:
+ from .resources.generate import GenerateResource
+
+ return GenerateResource(self)
+
+ @cached_property
+ def integration(self) -> IntegrationResource:
+ from .resources.integration import IntegrationResource
+
+ return IntegrationResource(self)
+
+ @cached_property
+ def location(self) -> LocationResource:
+ from .resources.location import LocationResource
+
+ return LocationResource(self)
+
+ @cached_property
+ def mcp(self) -> McpResource:
+ from .resources.mcp import McpResource
+
+ return McpResource(self)
+
+ @cached_property
+ def message(self) -> MessageResource:
+ from .resources.message import MessageResource
- wait_until_ready(self, agents=agents, mcp_servers=mcp_servers, timeout=timeout)
+ return MessageResource(self)
- def with_options(
+ @cached_property
+ def migration(self) -> MigrationResource:
+ from .resources.migration import MigrationResource
+
+ return MigrationResource(self)
+
+ @cached_property
+ def model(self) -> ModelResource:
+ from .resources.model import ModelResource
+
+ return ModelResource(self)
+
+ @cached_property
+ def permission(self) -> PermissionResource:
+ from .resources.permission import PermissionResource
+
+ return PermissionResource(self)
+
+ @cached_property
+ def plugin(self) -> PluginResource:
+ from .resources.plugin import PluginResource
+
+ return PluginResource(self)
+
+ @cached_property
+ def project(self) -> ProjectResource:
+ from .resources.project import ProjectResource
+
+ return ProjectResource(self)
+
+ @cached_property
+ def provider(self) -> ProviderResource:
+ from .resources.provider import ProviderResource
+
+ return ProviderResource(self)
+
+ @cached_property
+ def pty(self) -> PtyResource:
+ from .resources.pty import PtyResource
+
+ return PtyResource(self)
+
+ @cached_property
+ def reference(self) -> ReferenceResource:
+ from .resources.reference import ReferenceResource
+
+ return ReferenceResource(self)
+
+ @cached_property
+ def rpc(self) -> RpcResource:
+ from .resources.rpc import RpcResource
+
+ return RpcResource(self)
+
+ @cached_property
+ def server(self) -> ServerResource:
+ from .resources.server import ServerResource
+
+ return ServerResource(self)
+
+ @cached_property
+ def session(self) -> SessionResource:
+ from .resources.session import SessionResource
+
+ return SessionResource(self)
+
+ @cached_property
+ def shell(self) -> ShellResource:
+ from .resources.shell import ShellResource
+
+ return ShellResource(self)
+
+ @cached_property
+ def skill(self) -> SkillResource:
+ from .resources.skill import SkillResource
+
+ return SkillResource(self)
+
+ @cached_property
+ def vcs(self) -> VcsResource:
+ from .resources.vcs import VcsResource
+
+ return VcsResource(self)
+
+ @cached_property
+ def voice(self) -> VoiceResource:
+ from .resources.voice import VoiceResource
+
+ return VoiceResource(self)
+
+ @cached_property
+ def websearch(self) -> WebsearchResource:
+ from .resources.websearch import WebsearchResource
+
+ return WebsearchResource(self)
+
+ @cached_property
+ def worktree(self) -> WorktreeResource:
+ from .resources.worktree import WorktreeResource
+
+ return WorktreeResource(self)
+
+ @cached_property
+ def with_raw_response(self) -> OpencodeWithRawResponse:
+ return OpencodeWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> OpencodeWithStreamedResponse:
+ return OpencodeWithStreamedResponse(self)
+
+ @property
+ @override
+ def qs(self) -> Querystring:
+ return Querystring(array_format="repeat")
+
+ @property
+ @override
+ def auth_headers(self) -> dict[str, str]:
+ return {}
+
+ @property
+ @override
+ def default_headers(self) -> dict[str, str | Omit]:
+ return {
+ **super().default_headers,
+ "X-Stainless-Async": "false",
+ **self._custom_headers,
+ }
+
+ def copy(
self,
*,
+ username: str | None = None,
+ password: str | None = None,
base_url: str | httpx.URL | None = None,
- password: str | NotGiven | None = NOT_GIVEN,
- directory: str | os.PathLike[str] | NotGiven | None = NOT_GIVEN,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- max_retries: int | None = None,
+ timeout: float | Timeout | None | NotGiven = not_given,
+ http_client: httpx.Client | None = None,
+ max_retries: int | NotGiven = not_given,
default_headers: Mapping[str, str] | None = None,
+ set_default_headers: Mapping[str, str] | None = None,
default_query: Mapping[str, object] | None = None,
+ set_default_query: Mapping[str, object] | None = None,
+ _extra_kwargs: Mapping[str, Any] = {},
) -> Self:
- """Returns a client with some options changed. It shares this client's connection pool.
-
- ``default_headers`` and ``default_query`` are merged over the current values. Closing the copy leaves the
- pool open; it closes with this client. Also available as ``copy``.
"""
- client = type(self)(
+ Create a new client instance re-using the same options given to the current client with optional overriding.
+ """
+ if default_headers is not None and set_default_headers is not None:
+ raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")
+
+ if default_query is not None and set_default_query is not None:
+ raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")
+
+ headers = self._custom_headers
+ if default_headers is not None:
+ headers = {**headers, **default_headers}
+ elif set_default_headers is not None:
+ headers = set_default_headers
+
+ params = self._custom_query
+ if default_query is not None:
+ params = {**params, **default_query}
+ elif set_default_query is not None:
+ params = set_default_query
+
+ http_client = http_client or self._client
+ return self.__class__(
+ username=username or self.username,
+ password=password or self.password,
base_url=base_url or self.base_url,
- password=self.password or "" if isinstance(password, NotGiven) else password,
- username=self.username,
- directory=self.directory if isinstance(directory, NotGiven) else directory,
timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
- max_retries=self.max_retries if max_retries is None else max_retries,
- default_headers={**self._default_headers, **(default_headers or {})},
- default_query={**self._default_query, **(default_query or {})},
- http_client=self._http,
+ http_client=http_client,
+ max_retries=max_retries if is_given(max_retries) else self.max_retries,
+ default_headers=headers,
+ default_query=params,
+ **_extra_kwargs,
)
- return client
- copy = with_options
+ # Alias for `copy` for nicer inline usage, e.g.
+ # client.with_options(timeout=10).foo.create(...)
+ with_options = copy
+
+ @override
+ def _make_status_error(
+ self,
+ err_msg: str,
+ *,
+ body: object,
+ response: httpx.Response,
+ ) -> APIStatusError:
+ if response.status_code == 400:
+ return _exceptions.BadRequestError(err_msg, response=response, body=body)
+
+ if response.status_code == 401:
+ return _exceptions.AuthenticationError(err_msg, response=response, body=body)
+
+ if response.status_code == 403:
+ return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)
+ if response.status_code == 404:
+ return _exceptions.NotFoundError(err_msg, response=response, body=body)
-class AsyncOpencode(AsyncAPIClient, AsyncResources):
- """Asynchronous client for an OpenCode server. Works with asyncio and trio.
+ if response.status_code == 409:
+ return _exceptions.ConflictError(err_msg, response=response, body=body)
- Takes the same arguments as :class:`Opencode`; ``http_client`` is an ``httpx.AsyncClient``.
+ if response.status_code == 422:
+ return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)
- Example::
+ if response.status_code == 429:
+ return _exceptions.RateLimitError(err_msg, response=response, body=body)
- from opencode import AsyncOpencode
+ if response.status_code >= 500:
+ return _exceptions.InternalServerError(err_msg, response=response, body=body)
+ return APIStatusError(err_msg, response=response, body=body)
- async with AsyncOpencode("http://127.0.0.1:4096", password="...") as client:
- for agent in await client.agent.list():
- print(agent.id)
- """
+
+class AsyncOpencode(AsyncAPIClient):
+ # client options
+ username: str | None
+ password: str | None
def __init__(
self,
- base_url: str | httpx.URL | None = None,
*,
+ username: str | None = None,
password: str | None = None,
- username: str = DEFAULT_USERNAME,
- directory: str | os.PathLike[str] | None = None,
- timeout: Timeout = DEFAULT_TIMEOUT,
+ base_url: str | httpx.URL | None = None,
+ timeout: float | Timeout | None | NotGiven = not_given,
max_retries: int = DEFAULT_MAX_RETRIES,
default_headers: Mapping[str, str] | None = None,
default_query: Mapping[str, object] | None = None,
+ # Configure a custom httpx client.
+ # We provide a `DefaultAsyncHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`.
+ # See the [httpx documentation](https://www.python-httpx.org/api/#asyncclient) for more details.
http_client: httpx.AsyncClient | None = None,
- transport: httpx.AsyncBaseTransport | None = None,
+ # Enable or disable schema validation for data returned by the API.
+ # When enabled an error APIResponseValidationError is raised
+ # if the API responds with invalid data for the expected schema.
+ #
+ # This parameter may be removed or changed in the future.
+ # If you rely on this feature, please open a GitHub issue
+ # outlining your use-case to help us decide if it should be
+ # part of our public interface in the future.
+ _strict_response_validation: bool = False,
) -> None:
+ """Construct a new async AsyncOpencode client instance.
+
+ This automatically infers the `password` argument from the `OPENCODE_PASSWORD` environment variable if it is not provided.
+ """
+ if username is None:
+ username = "opencode"
+ self.username = username
+
+ if password is None:
+ password = os.environ.get("OPENCODE_PASSWORD")
+ self.password = password
+
+ if base_url is None:
+ base_url = os.environ.get("OPENCODE_BASE_URL")
+ if base_url is None:
+ base_url = f"http://127.0.0.1:4096"
+
+ custom_headers_env = os.environ.get("OPENCODE_CUSTOM_HEADERS")
+ if custom_headers_env is not None:
+ parsed: dict[str, str] = {}
+ for line in custom_headers_env.split("\n"):
+ colon = line.find(":")
+ if colon >= 0:
+ parsed[line[:colon].strip()] = line[colon + 1 :].strip()
+ default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}
+
super().__init__(
+ version=__version__,
base_url=base_url,
- password=password,
- username=username,
- directory=directory,
- timeout=timeout,
max_retries=max_retries,
- default_headers=default_headers,
- default_query=default_query,
+ timeout=timeout,
http_client=http_client,
- transport=transport,
+ custom_headers=default_headers,
+ custom_query=default_query,
+ _strict_response_validation=_strict_response_validation,
)
- @classmethod
- async def from_service(
- cls,
- *,
- start: bool = True,
- file: str | os.PathLike[str] | None = None,
- version: Version | None = None,
- command: Sequence[str] | None = None,
- env: Mapping[str, str] | None = None,
- on_start: Callable[[EnsureReason, str | None], None] | None = None,
- directory: str | os.PathLike[str] | None = None,
- timeout: Timeout = DEFAULT_TIMEOUT,
- max_retries: int = DEFAULT_MAX_RETRIES,
- default_headers: Mapping[str, str] | None = None,
- default_query: Mapping[str, object] | None = None,
- ) -> Self:
- """Async version of :meth:`Opencode.from_service`. Await it: ``client = await AsyncOpencode.from_service()``."""
- if start:
- endpoint = await async_ensure(file, version, command=command, env=env, on_start=on_start)
- else:
- found = await async_discover(file, version)
- if found is None:
- raise ServiceError("No matching OpenCode background service is running")
- endpoint = found
- return cls(
- endpoint.url,
- # An empty password sends no credentials instead of falling back to OPENCODE_PASSWORD.
- password="" if endpoint.auth is None else endpoint.auth.password,
- username=DEFAULT_USERNAME if endpoint.auth is None else endpoint.auth.username,
- directory=directory,
- timeout=timeout,
- max_retries=max_retries,
- default_headers=default_headers,
- default_query=default_query,
- )
+ @cached_property
+ def agent(self) -> AsyncAgentResource:
+ from .resources.agent import AsyncAgentResource
+
+ return AsyncAgentResource(self)
@cached_property
- def events(self) -> AsyncEventHub:
- """The shared event stream: one connection for every subscriber. See :class:`opencode.AsyncEventHub`."""
- return AsyncEventHub(self)
+ def command(self) -> AsyncCommandResource:
+ from .resources.command import AsyncCommandResource
+
+ return AsyncCommandResource(self)
@cached_property
- def rpc(self) -> AsyncTypedRpcResource:
- """Plugin RPC calls: ``client.rpc.call(...)``, or ``client.rpc(Definition)`` for typed access."""
- return AsyncTypedRpcResource(self)
+ def config(self) -> AsyncConfigResource:
+ from .resources.config import AsyncConfigResource
- @classmethod
- def fake(cls, server: FakeServer | None = None, **kwargs: Any) -> Self:
- """Returns a client wired to an in-process :class:`opencode.testing.FakeServer`, for tests.
+ return AsyncConfigResource(self)
- Pass your own ``server`` to configure replies and inspect requests; other arguments go to the constructor.
- """
- from .testing import FakeServer
+ @cached_property
+ def credential(self) -> AsyncCredentialResource:
+ from .resources.credential import AsyncCredentialResource
- kwargs.setdefault("password", "")
- return cls(FAKE_BASE_URL, transport=server if server is not None else FakeServer(), **kwargs)
+ return AsyncCredentialResource(self)
- async def wait_until_ready(
- self, *, agents: Iterable[str] = (), mcp_servers: Iterable[str] = (), timeout: float = 60
- ) -> None:
- """Blocks until the server answers, the named ``agents`` are listed and the named ``mcp_servers`` are
- connected.
+ @cached_property
+ def debug(self) -> AsyncDebugResource:
+ from .resources.debug import AsyncDebugResource
- Agents load asynchronously after the server starts, so call this before creating sessions on a freshly
- booted server. Connection errors and 503s while the server boots count as not ready yet.
+ return AsyncDebugResource(self)
- Raises:
- TimeoutError: Something was still missing after ``timeout`` seconds. The message names it.
- """
- from .lib._ready import async_wait_until_ready
+ @cached_property
+ def event(self) -> AsyncEventResource:
+ from .resources.event import AsyncEventResource
+
+ return AsyncEventResource(self)
+
+ @cached_property
+ def experimental(self) -> AsyncExperimentalResource:
+ from .resources.experimental import AsyncExperimentalResource
+
+ return AsyncExperimentalResource(self)
+
+ @cached_property
+ def file(self) -> AsyncFileResource:
+ from .resources.file import AsyncFileResource
+
+ return AsyncFileResource(self)
+
+ @cached_property
+ def form(self) -> AsyncFormResource:
+ from .resources.form import AsyncFormResource
+
+ return AsyncFormResource(self)
+
+ @cached_property
+ def generate(self) -> AsyncGenerateResource:
+ from .resources.generate import AsyncGenerateResource
+
+ return AsyncGenerateResource(self)
+
+ @cached_property
+ def integration(self) -> AsyncIntegrationResource:
+ from .resources.integration import AsyncIntegrationResource
+
+ return AsyncIntegrationResource(self)
+
+ @cached_property
+ def location(self) -> AsyncLocationResource:
+ from .resources.location import AsyncLocationResource
+
+ return AsyncLocationResource(self)
+
+ @cached_property
+ def mcp(self) -> AsyncMcpResource:
+ from .resources.mcp import AsyncMcpResource
+
+ return AsyncMcpResource(self)
+
+ @cached_property
+ def message(self) -> AsyncMessageResource:
+ from .resources.message import AsyncMessageResource
+
+ return AsyncMessageResource(self)
+
+ @cached_property
+ def migration(self) -> AsyncMigrationResource:
+ from .resources.migration import AsyncMigrationResource
+
+ return AsyncMigrationResource(self)
- await async_wait_until_ready(self, agents=agents, mcp_servers=mcp_servers, timeout=timeout)
+ @cached_property
+ def model(self) -> AsyncModelResource:
+ from .resources.model import AsyncModelResource
+
+ return AsyncModelResource(self)
+
+ @cached_property
+ def permission(self) -> AsyncPermissionResource:
+ from .resources.permission import AsyncPermissionResource
+
+ return AsyncPermissionResource(self)
+
+ @cached_property
+ def plugin(self) -> AsyncPluginResource:
+ from .resources.plugin import AsyncPluginResource
+
+ return AsyncPluginResource(self)
+
+ @cached_property
+ def project(self) -> AsyncProjectResource:
+ from .resources.project import AsyncProjectResource
+
+ return AsyncProjectResource(self)
+
+ @cached_property
+ def provider(self) -> AsyncProviderResource:
+ from .resources.provider import AsyncProviderResource
+
+ return AsyncProviderResource(self)
+
+ @cached_property
+ def pty(self) -> AsyncPtyResource:
+ from .resources.pty import AsyncPtyResource
+
+ return AsyncPtyResource(self)
+
+ @cached_property
+ def reference(self) -> AsyncReferenceResource:
+ from .resources.reference import AsyncReferenceResource
+
+ return AsyncReferenceResource(self)
+
+ @cached_property
+ def rpc(self) -> AsyncRpcResource:
+ from .resources.rpc import AsyncRpcResource
+
+ return AsyncRpcResource(self)
+
+ @cached_property
+ def server(self) -> AsyncServerResource:
+ from .resources.server import AsyncServerResource
+
+ return AsyncServerResource(self)
+
+ @cached_property
+ def session(self) -> AsyncSessionResource:
+ from .resources.session import AsyncSessionResource
+
+ return AsyncSessionResource(self)
+
+ @cached_property
+ def shell(self) -> AsyncShellResource:
+ from .resources.shell import AsyncShellResource
+
+ return AsyncShellResource(self)
- def with_options(
+ @cached_property
+ def skill(self) -> AsyncSkillResource:
+ from .resources.skill import AsyncSkillResource
+
+ return AsyncSkillResource(self)
+
+ @cached_property
+ def vcs(self) -> AsyncVcsResource:
+ from .resources.vcs import AsyncVcsResource
+
+ return AsyncVcsResource(self)
+
+ @cached_property
+ def voice(self) -> AsyncVoiceResource:
+ from .resources.voice import AsyncVoiceResource
+
+ return AsyncVoiceResource(self)
+
+ @cached_property
+ def websearch(self) -> AsyncWebsearchResource:
+ from .resources.websearch import AsyncWebsearchResource
+
+ return AsyncWebsearchResource(self)
+
+ @cached_property
+ def worktree(self) -> AsyncWorktreeResource:
+ from .resources.worktree import AsyncWorktreeResource
+
+ return AsyncWorktreeResource(self)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncOpencodeWithRawResponse:
+ return AsyncOpencodeWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncOpencodeWithStreamedResponse:
+ return AsyncOpencodeWithStreamedResponse(self)
+
+ @property
+ @override
+ def qs(self) -> Querystring:
+ return Querystring(array_format="repeat")
+
+ @property
+ @override
+ def auth_headers(self) -> dict[str, str]:
+ return {}
+
+ @property
+ @override
+ def default_headers(self) -> dict[str, str | Omit]:
+ return {
+ **super().default_headers,
+ "X-Stainless-Async": f"async:{get_async_library()}",
+ **self._custom_headers,
+ }
+
+ def copy(
self,
*,
+ username: str | None = None,
+ password: str | None = None,
base_url: str | httpx.URL | None = None,
- password: str | NotGiven | None = NOT_GIVEN,
- directory: str | os.PathLike[str] | NotGiven | None = NOT_GIVEN,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- max_retries: int | None = None,
+ timeout: float | Timeout | None | NotGiven = not_given,
+ http_client: httpx.AsyncClient | None = None,
+ max_retries: int | NotGiven = not_given,
default_headers: Mapping[str, str] | None = None,
+ set_default_headers: Mapping[str, str] | None = None,
default_query: Mapping[str, object] | None = None,
+ set_default_query: Mapping[str, object] | None = None,
+ _extra_kwargs: Mapping[str, Any] = {},
) -> Self:
- """Returns a client with some options changed. It shares this client's connection pool.
-
- ``default_headers`` and ``default_query`` are merged over the current values. Closing the copy leaves the
- pool open; it closes with this client. Also available as ``copy``.
"""
- client = type(self)(
+ Create a new client instance re-using the same options given to the current client with optional overriding.
+ """
+ if default_headers is not None and set_default_headers is not None:
+ raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")
+
+ if default_query is not None and set_default_query is not None:
+ raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")
+
+ headers = self._custom_headers
+ if default_headers is not None:
+ headers = {**headers, **default_headers}
+ elif set_default_headers is not None:
+ headers = set_default_headers
+
+ params = self._custom_query
+ if default_query is not None:
+ params = {**params, **default_query}
+ elif set_default_query is not None:
+ params = set_default_query
+
+ http_client = http_client or self._client
+ return self.__class__(
+ username=username or self.username,
+ password=password or self.password,
base_url=base_url or self.base_url,
- password=self.password or "" if isinstance(password, NotGiven) else password,
- username=self.username,
- directory=self.directory if isinstance(directory, NotGiven) else directory,
timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
- max_retries=self.max_retries if max_retries is None else max_retries,
- default_headers={**self._default_headers, **(default_headers or {})},
- default_query={**self._default_query, **(default_query or {})},
- http_client=self._http,
+ http_client=http_client,
+ max_retries=max_retries if is_given(max_retries) else self.max_retries,
+ default_headers=headers,
+ default_query=params,
+ **_extra_kwargs,
)
- return client
- copy = with_options
+ # Alias for `copy` for nicer inline usage, e.g.
+ # client.with_options(timeout=10).foo.create(...)
+ with_options = copy
+
+ @override
+ def _make_status_error(
+ self,
+ err_msg: str,
+ *,
+ body: object,
+ response: httpx.Response,
+ ) -> APIStatusError:
+ if response.status_code == 400:
+ return _exceptions.BadRequestError(err_msg, response=response, body=body)
+
+ if response.status_code == 401:
+ return _exceptions.AuthenticationError(err_msg, response=response, body=body)
+
+ if response.status_code == 403:
+ return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)
+
+ if response.status_code == 404:
+ return _exceptions.NotFoundError(err_msg, response=response, body=body)
+
+ if response.status_code == 409:
+ return _exceptions.ConflictError(err_msg, response=response, body=body)
+
+ if response.status_code == 422:
+ return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)
+
+ if response.status_code == 429:
+ return _exceptions.RateLimitError(err_msg, response=response, body=body)
+
+ if response.status_code >= 500:
+ return _exceptions.InternalServerError(err_msg, response=response, body=body)
+ return APIStatusError(err_msg, response=response, body=body)
+
+
+class OpencodeWithRawResponse:
+ _client: Opencode
+
+ def __init__(self, client: Opencode) -> None:
+ self._client = client
+
+ @cached_property
+ def agent(self) -> agent.AgentResourceWithRawResponse:
+ from .resources.agent import AgentResourceWithRawResponse
+
+ return AgentResourceWithRawResponse(self._client.agent)
+
+ @cached_property
+ def command(self) -> command.CommandResourceWithRawResponse:
+ from .resources.command import CommandResourceWithRawResponse
+
+ return CommandResourceWithRawResponse(self._client.command)
+
+ @cached_property
+ def config(self) -> config.ConfigResourceWithRawResponse:
+ from .resources.config import ConfigResourceWithRawResponse
+
+ return ConfigResourceWithRawResponse(self._client.config)
+
+ @cached_property
+ def credential(self) -> credential.CredentialResourceWithRawResponse:
+ from .resources.credential import CredentialResourceWithRawResponse
+
+ return CredentialResourceWithRawResponse(self._client.credential)
+
+ @cached_property
+ def debug(self) -> debug.DebugResourceWithRawResponse:
+ from .resources.debug import DebugResourceWithRawResponse
+
+ return DebugResourceWithRawResponse(self._client.debug)
+
+ @cached_property
+ def event(self) -> event.EventResourceWithRawResponse:
+ from .resources.event import EventResourceWithRawResponse
+
+ return EventResourceWithRawResponse(self._client.event)
+
+ @cached_property
+ def experimental(self) -> experimental.ExperimentalResourceWithRawResponse:
+ from .resources.experimental import ExperimentalResourceWithRawResponse
+
+ return ExperimentalResourceWithRawResponse(self._client.experimental)
+
+ @cached_property
+ def file(self) -> file.FileResourceWithRawResponse:
+ from .resources.file import FileResourceWithRawResponse
+
+ return FileResourceWithRawResponse(self._client.file)
+
+ @cached_property
+ def form(self) -> form.FormResourceWithRawResponse:
+ from .resources.form import FormResourceWithRawResponse
+
+ return FormResourceWithRawResponse(self._client.form)
+
+ @cached_property
+ def generate(self) -> generate.GenerateResourceWithRawResponse:
+ from .resources.generate import GenerateResourceWithRawResponse
+
+ return GenerateResourceWithRawResponse(self._client.generate)
+
+ @cached_property
+ def integration(self) -> integration.IntegrationResourceWithRawResponse:
+ from .resources.integration import IntegrationResourceWithRawResponse
+
+ return IntegrationResourceWithRawResponse(self._client.integration)
+
+ @cached_property
+ def location(self) -> location.LocationResourceWithRawResponse:
+ from .resources.location import LocationResourceWithRawResponse
+
+ return LocationResourceWithRawResponse(self._client.location)
+
+ @cached_property
+ def mcp(self) -> mcp.McpResourceWithRawResponse:
+ from .resources.mcp import McpResourceWithRawResponse
+
+ return McpResourceWithRawResponse(self._client.mcp)
+
+ @cached_property
+ def message(self) -> message.MessageResourceWithRawResponse:
+ from .resources.message import MessageResourceWithRawResponse
+
+ return MessageResourceWithRawResponse(self._client.message)
+
+ @cached_property
+ def migration(self) -> migration.MigrationResourceWithRawResponse:
+ from .resources.migration import MigrationResourceWithRawResponse
+
+ return MigrationResourceWithRawResponse(self._client.migration)
+
+ @cached_property
+ def model(self) -> model.ModelResourceWithRawResponse:
+ from .resources.model import ModelResourceWithRawResponse
+
+ return ModelResourceWithRawResponse(self._client.model)
+
+ @cached_property
+ def permission(self) -> permission.PermissionResourceWithRawResponse:
+ from .resources.permission import PermissionResourceWithRawResponse
+
+ return PermissionResourceWithRawResponse(self._client.permission)
+
+ @cached_property
+ def plugin(self) -> plugin.PluginResourceWithRawResponse:
+ from .resources.plugin import PluginResourceWithRawResponse
+
+ return PluginResourceWithRawResponse(self._client.plugin)
+
+ @cached_property
+ def project(self) -> project.ProjectResourceWithRawResponse:
+ from .resources.project import ProjectResourceWithRawResponse
+
+ return ProjectResourceWithRawResponse(self._client.project)
+
+ @cached_property
+ def provider(self) -> provider.ProviderResourceWithRawResponse:
+ from .resources.provider import ProviderResourceWithRawResponse
+
+ return ProviderResourceWithRawResponse(self._client.provider)
+
+ @cached_property
+ def pty(self) -> pty.PtyResourceWithRawResponse:
+ from .resources.pty import PtyResourceWithRawResponse
+
+ return PtyResourceWithRawResponse(self._client.pty)
+
+ @cached_property
+ def reference(self) -> reference.ReferenceResourceWithRawResponse:
+ from .resources.reference import ReferenceResourceWithRawResponse
+
+ return ReferenceResourceWithRawResponse(self._client.reference)
+
+ @cached_property
+ def rpc(self) -> rpc.RpcResourceWithRawResponse:
+ from .resources.rpc import RpcResourceWithRawResponse
+
+ return RpcResourceWithRawResponse(self._client.rpc)
+
+ @cached_property
+ def server(self) -> server.ServerResourceWithRawResponse:
+ from .resources.server import ServerResourceWithRawResponse
+
+ return ServerResourceWithRawResponse(self._client.server)
+
+ @cached_property
+ def session(self) -> session.SessionResourceWithRawResponse:
+ from .resources.session import SessionResourceWithRawResponse
+
+ return SessionResourceWithRawResponse(self._client.session)
+
+ @cached_property
+ def shell(self) -> shell.ShellResourceWithRawResponse:
+ from .resources.shell import ShellResourceWithRawResponse
+
+ return ShellResourceWithRawResponse(self._client.shell)
+
+ @cached_property
+ def skill(self) -> skill.SkillResourceWithRawResponse:
+ from .resources.skill import SkillResourceWithRawResponse
+
+ return SkillResourceWithRawResponse(self._client.skill)
+
+ @cached_property
+ def vcs(self) -> vcs.VcsResourceWithRawResponse:
+ from .resources.vcs import VcsResourceWithRawResponse
+
+ return VcsResourceWithRawResponse(self._client.vcs)
+
+ @cached_property
+ def voice(self) -> voice.VoiceResourceWithRawResponse:
+ from .resources.voice import VoiceResourceWithRawResponse
+
+ return VoiceResourceWithRawResponse(self._client.voice)
+
+ @cached_property
+ def websearch(self) -> websearch.WebsearchResourceWithRawResponse:
+ from .resources.websearch import WebsearchResourceWithRawResponse
+
+ return WebsearchResourceWithRawResponse(self._client.websearch)
+
+ @cached_property
+ def worktree(self) -> worktree.WorktreeResourceWithRawResponse:
+ from .resources.worktree import WorktreeResourceWithRawResponse
+
+ return WorktreeResourceWithRawResponse(self._client.worktree)
+
+
+class AsyncOpencodeWithRawResponse:
+ _client: AsyncOpencode
+
+ def __init__(self, client: AsyncOpencode) -> None:
+ self._client = client
+
+ @cached_property
+ def agent(self) -> agent.AsyncAgentResourceWithRawResponse:
+ from .resources.agent import AsyncAgentResourceWithRawResponse
+
+ return AsyncAgentResourceWithRawResponse(self._client.agent)
+
+ @cached_property
+ def command(self) -> command.AsyncCommandResourceWithRawResponse:
+ from .resources.command import AsyncCommandResourceWithRawResponse
+
+ return AsyncCommandResourceWithRawResponse(self._client.command)
+
+ @cached_property
+ def config(self) -> config.AsyncConfigResourceWithRawResponse:
+ from .resources.config import AsyncConfigResourceWithRawResponse
+
+ return AsyncConfigResourceWithRawResponse(self._client.config)
+
+ @cached_property
+ def credential(self) -> credential.AsyncCredentialResourceWithRawResponse:
+ from .resources.credential import AsyncCredentialResourceWithRawResponse
+
+ return AsyncCredentialResourceWithRawResponse(self._client.credential)
+
+ @cached_property
+ def debug(self) -> debug.AsyncDebugResourceWithRawResponse:
+ from .resources.debug import AsyncDebugResourceWithRawResponse
+
+ return AsyncDebugResourceWithRawResponse(self._client.debug)
+
+ @cached_property
+ def event(self) -> event.AsyncEventResourceWithRawResponse:
+ from .resources.event import AsyncEventResourceWithRawResponse
+
+ return AsyncEventResourceWithRawResponse(self._client.event)
+
+ @cached_property
+ def experimental(self) -> experimental.AsyncExperimentalResourceWithRawResponse:
+ from .resources.experimental import AsyncExperimentalResourceWithRawResponse
+
+ return AsyncExperimentalResourceWithRawResponse(self._client.experimental)
+
+ @cached_property
+ def file(self) -> file.AsyncFileResourceWithRawResponse:
+ from .resources.file import AsyncFileResourceWithRawResponse
+
+ return AsyncFileResourceWithRawResponse(self._client.file)
+
+ @cached_property
+ def form(self) -> form.AsyncFormResourceWithRawResponse:
+ from .resources.form import AsyncFormResourceWithRawResponse
+
+ return AsyncFormResourceWithRawResponse(self._client.form)
+
+ @cached_property
+ def generate(self) -> generate.AsyncGenerateResourceWithRawResponse:
+ from .resources.generate import AsyncGenerateResourceWithRawResponse
+
+ return AsyncGenerateResourceWithRawResponse(self._client.generate)
+
+ @cached_property
+ def integration(self) -> integration.AsyncIntegrationResourceWithRawResponse:
+ from .resources.integration import AsyncIntegrationResourceWithRawResponse
+
+ return AsyncIntegrationResourceWithRawResponse(self._client.integration)
+
+ @cached_property
+ def location(self) -> location.AsyncLocationResourceWithRawResponse:
+ from .resources.location import AsyncLocationResourceWithRawResponse
+
+ return AsyncLocationResourceWithRawResponse(self._client.location)
+
+ @cached_property
+ def mcp(self) -> mcp.AsyncMcpResourceWithRawResponse:
+ from .resources.mcp import AsyncMcpResourceWithRawResponse
+
+ return AsyncMcpResourceWithRawResponse(self._client.mcp)
+
+ @cached_property
+ def message(self) -> message.AsyncMessageResourceWithRawResponse:
+ from .resources.message import AsyncMessageResourceWithRawResponse
+
+ return AsyncMessageResourceWithRawResponse(self._client.message)
+
+ @cached_property
+ def migration(self) -> migration.AsyncMigrationResourceWithRawResponse:
+ from .resources.migration import AsyncMigrationResourceWithRawResponse
+
+ return AsyncMigrationResourceWithRawResponse(self._client.migration)
+
+ @cached_property
+ def model(self) -> model.AsyncModelResourceWithRawResponse:
+ from .resources.model import AsyncModelResourceWithRawResponse
+
+ return AsyncModelResourceWithRawResponse(self._client.model)
+
+ @cached_property
+ def permission(self) -> permission.AsyncPermissionResourceWithRawResponse:
+ from .resources.permission import AsyncPermissionResourceWithRawResponse
+
+ return AsyncPermissionResourceWithRawResponse(self._client.permission)
+
+ @cached_property
+ def plugin(self) -> plugin.AsyncPluginResourceWithRawResponse:
+ from .resources.plugin import AsyncPluginResourceWithRawResponse
+
+ return AsyncPluginResourceWithRawResponse(self._client.plugin)
+
+ @cached_property
+ def project(self) -> project.AsyncProjectResourceWithRawResponse:
+ from .resources.project import AsyncProjectResourceWithRawResponse
+
+ return AsyncProjectResourceWithRawResponse(self._client.project)
+
+ @cached_property
+ def provider(self) -> provider.AsyncProviderResourceWithRawResponse:
+ from .resources.provider import AsyncProviderResourceWithRawResponse
+
+ return AsyncProviderResourceWithRawResponse(self._client.provider)
+
+ @cached_property
+ def pty(self) -> pty.AsyncPtyResourceWithRawResponse:
+ from .resources.pty import AsyncPtyResourceWithRawResponse
+
+ return AsyncPtyResourceWithRawResponse(self._client.pty)
+
+ @cached_property
+ def reference(self) -> reference.AsyncReferenceResourceWithRawResponse:
+ from .resources.reference import AsyncReferenceResourceWithRawResponse
+
+ return AsyncReferenceResourceWithRawResponse(self._client.reference)
+
+ @cached_property
+ def rpc(self) -> rpc.AsyncRpcResourceWithRawResponse:
+ from .resources.rpc import AsyncRpcResourceWithRawResponse
+
+ return AsyncRpcResourceWithRawResponse(self._client.rpc)
+
+ @cached_property
+ def server(self) -> server.AsyncServerResourceWithRawResponse:
+ from .resources.server import AsyncServerResourceWithRawResponse
+
+ return AsyncServerResourceWithRawResponse(self._client.server)
+
+ @cached_property
+ def session(self) -> session.AsyncSessionResourceWithRawResponse:
+ from .resources.session import AsyncSessionResourceWithRawResponse
+
+ return AsyncSessionResourceWithRawResponse(self._client.session)
+
+ @cached_property
+ def shell(self) -> shell.AsyncShellResourceWithRawResponse:
+ from .resources.shell import AsyncShellResourceWithRawResponse
+
+ return AsyncShellResourceWithRawResponse(self._client.shell)
+
+ @cached_property
+ def skill(self) -> skill.AsyncSkillResourceWithRawResponse:
+ from .resources.skill import AsyncSkillResourceWithRawResponse
+
+ return AsyncSkillResourceWithRawResponse(self._client.skill)
+
+ @cached_property
+ def vcs(self) -> vcs.AsyncVcsResourceWithRawResponse:
+ from .resources.vcs import AsyncVcsResourceWithRawResponse
+
+ return AsyncVcsResourceWithRawResponse(self._client.vcs)
+
+ @cached_property
+ def voice(self) -> voice.AsyncVoiceResourceWithRawResponse:
+ from .resources.voice import AsyncVoiceResourceWithRawResponse
+
+ return AsyncVoiceResourceWithRawResponse(self._client.voice)
+
+ @cached_property
+ def websearch(self) -> websearch.AsyncWebsearchResourceWithRawResponse:
+ from .resources.websearch import AsyncWebsearchResourceWithRawResponse
+
+ return AsyncWebsearchResourceWithRawResponse(self._client.websearch)
+
+ @cached_property
+ def worktree(self) -> worktree.AsyncWorktreeResourceWithRawResponse:
+ from .resources.worktree import AsyncWorktreeResourceWithRawResponse
+
+ return AsyncWorktreeResourceWithRawResponse(self._client.worktree)
+
+
+class OpencodeWithStreamedResponse:
+ _client: Opencode
+
+ def __init__(self, client: Opencode) -> None:
+ self._client = client
+
+ @cached_property
+ def agent(self) -> agent.AgentResourceWithStreamingResponse:
+ from .resources.agent import AgentResourceWithStreamingResponse
+
+ return AgentResourceWithStreamingResponse(self._client.agent)
+
+ @cached_property
+ def command(self) -> command.CommandResourceWithStreamingResponse:
+ from .resources.command import CommandResourceWithStreamingResponse
+
+ return CommandResourceWithStreamingResponse(self._client.command)
+
+ @cached_property
+ def config(self) -> config.ConfigResourceWithStreamingResponse:
+ from .resources.config import ConfigResourceWithStreamingResponse
+
+ return ConfigResourceWithStreamingResponse(self._client.config)
+
+ @cached_property
+ def credential(self) -> credential.CredentialResourceWithStreamingResponse:
+ from .resources.credential import CredentialResourceWithStreamingResponse
+
+ return CredentialResourceWithStreamingResponse(self._client.credential)
+
+ @cached_property
+ def debug(self) -> debug.DebugResourceWithStreamingResponse:
+ from .resources.debug import DebugResourceWithStreamingResponse
+
+ return DebugResourceWithStreamingResponse(self._client.debug)
+
+ @cached_property
+ def event(self) -> event.EventResourceWithStreamingResponse:
+ from .resources.event import EventResourceWithStreamingResponse
+
+ return EventResourceWithStreamingResponse(self._client.event)
+
+ @cached_property
+ def experimental(self) -> experimental.ExperimentalResourceWithStreamingResponse:
+ from .resources.experimental import ExperimentalResourceWithStreamingResponse
+
+ return ExperimentalResourceWithStreamingResponse(self._client.experimental)
+
+ @cached_property
+ def file(self) -> file.FileResourceWithStreamingResponse:
+ from .resources.file import FileResourceWithStreamingResponse
+
+ return FileResourceWithStreamingResponse(self._client.file)
+
+ @cached_property
+ def form(self) -> form.FormResourceWithStreamingResponse:
+ from .resources.form import FormResourceWithStreamingResponse
+
+ return FormResourceWithStreamingResponse(self._client.form)
+
+ @cached_property
+ def generate(self) -> generate.GenerateResourceWithStreamingResponse:
+ from .resources.generate import GenerateResourceWithStreamingResponse
+
+ return GenerateResourceWithStreamingResponse(self._client.generate)
+
+ @cached_property
+ def integration(self) -> integration.IntegrationResourceWithStreamingResponse:
+ from .resources.integration import IntegrationResourceWithStreamingResponse
+
+ return IntegrationResourceWithStreamingResponse(self._client.integration)
+
+ @cached_property
+ def location(self) -> location.LocationResourceWithStreamingResponse:
+ from .resources.location import LocationResourceWithStreamingResponse
+
+ return LocationResourceWithStreamingResponse(self._client.location)
+
+ @cached_property
+ def mcp(self) -> mcp.McpResourceWithStreamingResponse:
+ from .resources.mcp import McpResourceWithStreamingResponse
+
+ return McpResourceWithStreamingResponse(self._client.mcp)
+
+ @cached_property
+ def message(self) -> message.MessageResourceWithStreamingResponse:
+ from .resources.message import MessageResourceWithStreamingResponse
+
+ return MessageResourceWithStreamingResponse(self._client.message)
+
+ @cached_property
+ def migration(self) -> migration.MigrationResourceWithStreamingResponse:
+ from .resources.migration import MigrationResourceWithStreamingResponse
+
+ return MigrationResourceWithStreamingResponse(self._client.migration)
+
+ @cached_property
+ def model(self) -> model.ModelResourceWithStreamingResponse:
+ from .resources.model import ModelResourceWithStreamingResponse
+
+ return ModelResourceWithStreamingResponse(self._client.model)
+
+ @cached_property
+ def permission(self) -> permission.PermissionResourceWithStreamingResponse:
+ from .resources.permission import PermissionResourceWithStreamingResponse
+
+ return PermissionResourceWithStreamingResponse(self._client.permission)
+
+ @cached_property
+ def plugin(self) -> plugin.PluginResourceWithStreamingResponse:
+ from .resources.plugin import PluginResourceWithStreamingResponse
+
+ return PluginResourceWithStreamingResponse(self._client.plugin)
+
+ @cached_property
+ def project(self) -> project.ProjectResourceWithStreamingResponse:
+ from .resources.project import ProjectResourceWithStreamingResponse
+
+ return ProjectResourceWithStreamingResponse(self._client.project)
+
+ @cached_property
+ def provider(self) -> provider.ProviderResourceWithStreamingResponse:
+ from .resources.provider import ProviderResourceWithStreamingResponse
+
+ return ProviderResourceWithStreamingResponse(self._client.provider)
+
+ @cached_property
+ def pty(self) -> pty.PtyResourceWithStreamingResponse:
+ from .resources.pty import PtyResourceWithStreamingResponse
+
+ return PtyResourceWithStreamingResponse(self._client.pty)
+
+ @cached_property
+ def reference(self) -> reference.ReferenceResourceWithStreamingResponse:
+ from .resources.reference import ReferenceResourceWithStreamingResponse
+
+ return ReferenceResourceWithStreamingResponse(self._client.reference)
+
+ @cached_property
+ def rpc(self) -> rpc.RpcResourceWithStreamingResponse:
+ from .resources.rpc import RpcResourceWithStreamingResponse
+
+ return RpcResourceWithStreamingResponse(self._client.rpc)
+
+ @cached_property
+ def server(self) -> server.ServerResourceWithStreamingResponse:
+ from .resources.server import ServerResourceWithStreamingResponse
+
+ return ServerResourceWithStreamingResponse(self._client.server)
+
+ @cached_property
+ def session(self) -> session.SessionResourceWithStreamingResponse:
+ from .resources.session import SessionResourceWithStreamingResponse
+
+ return SessionResourceWithStreamingResponse(self._client.session)
+
+ @cached_property
+ def shell(self) -> shell.ShellResourceWithStreamingResponse:
+ from .resources.shell import ShellResourceWithStreamingResponse
+
+ return ShellResourceWithStreamingResponse(self._client.shell)
+
+ @cached_property
+ def skill(self) -> skill.SkillResourceWithStreamingResponse:
+ from .resources.skill import SkillResourceWithStreamingResponse
+
+ return SkillResourceWithStreamingResponse(self._client.skill)
+
+ @cached_property
+ def vcs(self) -> vcs.VcsResourceWithStreamingResponse:
+ from .resources.vcs import VcsResourceWithStreamingResponse
+
+ return VcsResourceWithStreamingResponse(self._client.vcs)
+
+ @cached_property
+ def voice(self) -> voice.VoiceResourceWithStreamingResponse:
+ from .resources.voice import VoiceResourceWithStreamingResponse
+
+ return VoiceResourceWithStreamingResponse(self._client.voice)
+
+ @cached_property
+ def websearch(self) -> websearch.WebsearchResourceWithStreamingResponse:
+ from .resources.websearch import WebsearchResourceWithStreamingResponse
+
+ return WebsearchResourceWithStreamingResponse(self._client.websearch)
+
+ @cached_property
+ def worktree(self) -> worktree.WorktreeResourceWithStreamingResponse:
+ from .resources.worktree import WorktreeResourceWithStreamingResponse
+
+ return WorktreeResourceWithStreamingResponse(self._client.worktree)
+
+
+class AsyncOpencodeWithStreamedResponse:
+ _client: AsyncOpencode
+
+ def __init__(self, client: AsyncOpencode) -> None:
+ self._client = client
+
+ @cached_property
+ def agent(self) -> agent.AsyncAgentResourceWithStreamingResponse:
+ from .resources.agent import AsyncAgentResourceWithStreamingResponse
+
+ return AsyncAgentResourceWithStreamingResponse(self._client.agent)
+
+ @cached_property
+ def command(self) -> command.AsyncCommandResourceWithStreamingResponse:
+ from .resources.command import AsyncCommandResourceWithStreamingResponse
+
+ return AsyncCommandResourceWithStreamingResponse(self._client.command)
+
+ @cached_property
+ def config(self) -> config.AsyncConfigResourceWithStreamingResponse:
+ from .resources.config import AsyncConfigResourceWithStreamingResponse
+
+ return AsyncConfigResourceWithStreamingResponse(self._client.config)
+
+ @cached_property
+ def credential(self) -> credential.AsyncCredentialResourceWithStreamingResponse:
+ from .resources.credential import AsyncCredentialResourceWithStreamingResponse
+
+ return AsyncCredentialResourceWithStreamingResponse(self._client.credential)
+
+ @cached_property
+ def debug(self) -> debug.AsyncDebugResourceWithStreamingResponse:
+ from .resources.debug import AsyncDebugResourceWithStreamingResponse
+
+ return AsyncDebugResourceWithStreamingResponse(self._client.debug)
+
+ @cached_property
+ def event(self) -> event.AsyncEventResourceWithStreamingResponse:
+ from .resources.event import AsyncEventResourceWithStreamingResponse
+
+ return AsyncEventResourceWithStreamingResponse(self._client.event)
+
+ @cached_property
+ def experimental(self) -> experimental.AsyncExperimentalResourceWithStreamingResponse:
+ from .resources.experimental import AsyncExperimentalResourceWithStreamingResponse
+
+ return AsyncExperimentalResourceWithStreamingResponse(self._client.experimental)
+
+ @cached_property
+ def file(self) -> file.AsyncFileResourceWithStreamingResponse:
+ from .resources.file import AsyncFileResourceWithStreamingResponse
+
+ return AsyncFileResourceWithStreamingResponse(self._client.file)
+
+ @cached_property
+ def form(self) -> form.AsyncFormResourceWithStreamingResponse:
+ from .resources.form import AsyncFormResourceWithStreamingResponse
+
+ return AsyncFormResourceWithStreamingResponse(self._client.form)
+
+ @cached_property
+ def generate(self) -> generate.AsyncGenerateResourceWithStreamingResponse:
+ from .resources.generate import AsyncGenerateResourceWithStreamingResponse
+
+ return AsyncGenerateResourceWithStreamingResponse(self._client.generate)
+
+ @cached_property
+ def integration(self) -> integration.AsyncIntegrationResourceWithStreamingResponse:
+ from .resources.integration import AsyncIntegrationResourceWithStreamingResponse
+
+ return AsyncIntegrationResourceWithStreamingResponse(self._client.integration)
+
+ @cached_property
+ def location(self) -> location.AsyncLocationResourceWithStreamingResponse:
+ from .resources.location import AsyncLocationResourceWithStreamingResponse
+
+ return AsyncLocationResourceWithStreamingResponse(self._client.location)
+
+ @cached_property
+ def mcp(self) -> mcp.AsyncMcpResourceWithStreamingResponse:
+ from .resources.mcp import AsyncMcpResourceWithStreamingResponse
+
+ return AsyncMcpResourceWithStreamingResponse(self._client.mcp)
+
+ @cached_property
+ def message(self) -> message.AsyncMessageResourceWithStreamingResponse:
+ from .resources.message import AsyncMessageResourceWithStreamingResponse
+
+ return AsyncMessageResourceWithStreamingResponse(self._client.message)
+
+ @cached_property
+ def migration(self) -> migration.AsyncMigrationResourceWithStreamingResponse:
+ from .resources.migration import AsyncMigrationResourceWithStreamingResponse
+
+ return AsyncMigrationResourceWithStreamingResponse(self._client.migration)
+
+ @cached_property
+ def model(self) -> model.AsyncModelResourceWithStreamingResponse:
+ from .resources.model import AsyncModelResourceWithStreamingResponse
+
+ return AsyncModelResourceWithStreamingResponse(self._client.model)
+
+ @cached_property
+ def permission(self) -> permission.AsyncPermissionResourceWithStreamingResponse:
+ from .resources.permission import AsyncPermissionResourceWithStreamingResponse
+
+ return AsyncPermissionResourceWithStreamingResponse(self._client.permission)
+
+ @cached_property
+ def plugin(self) -> plugin.AsyncPluginResourceWithStreamingResponse:
+ from .resources.plugin import AsyncPluginResourceWithStreamingResponse
+
+ return AsyncPluginResourceWithStreamingResponse(self._client.plugin)
+
+ @cached_property
+ def project(self) -> project.AsyncProjectResourceWithStreamingResponse:
+ from .resources.project import AsyncProjectResourceWithStreamingResponse
+
+ return AsyncProjectResourceWithStreamingResponse(self._client.project)
+
+ @cached_property
+ def provider(self) -> provider.AsyncProviderResourceWithStreamingResponse:
+ from .resources.provider import AsyncProviderResourceWithStreamingResponse
+
+ return AsyncProviderResourceWithStreamingResponse(self._client.provider)
+
+ @cached_property
+ def pty(self) -> pty.AsyncPtyResourceWithStreamingResponse:
+ from .resources.pty import AsyncPtyResourceWithStreamingResponse
+
+ return AsyncPtyResourceWithStreamingResponse(self._client.pty)
+
+ @cached_property
+ def reference(self) -> reference.AsyncReferenceResourceWithStreamingResponse:
+ from .resources.reference import AsyncReferenceResourceWithStreamingResponse
+
+ return AsyncReferenceResourceWithStreamingResponse(self._client.reference)
+
+ @cached_property
+ def rpc(self) -> rpc.AsyncRpcResourceWithStreamingResponse:
+ from .resources.rpc import AsyncRpcResourceWithStreamingResponse
+
+ return AsyncRpcResourceWithStreamingResponse(self._client.rpc)
+
+ @cached_property
+ def server(self) -> server.AsyncServerResourceWithStreamingResponse:
+ from .resources.server import AsyncServerResourceWithStreamingResponse
+
+ return AsyncServerResourceWithStreamingResponse(self._client.server)
+
+ @cached_property
+ def session(self) -> session.AsyncSessionResourceWithStreamingResponse:
+ from .resources.session import AsyncSessionResourceWithStreamingResponse
+
+ return AsyncSessionResourceWithStreamingResponse(self._client.session)
+
+ @cached_property
+ def shell(self) -> shell.AsyncShellResourceWithStreamingResponse:
+ from .resources.shell import AsyncShellResourceWithStreamingResponse
+
+ return AsyncShellResourceWithStreamingResponse(self._client.shell)
+
+ @cached_property
+ def skill(self) -> skill.AsyncSkillResourceWithStreamingResponse:
+ from .resources.skill import AsyncSkillResourceWithStreamingResponse
+
+ return AsyncSkillResourceWithStreamingResponse(self._client.skill)
+
+ @cached_property
+ def vcs(self) -> vcs.AsyncVcsResourceWithStreamingResponse:
+ from .resources.vcs import AsyncVcsResourceWithStreamingResponse
+
+ return AsyncVcsResourceWithStreamingResponse(self._client.vcs)
+
+ @cached_property
+ def voice(self) -> voice.AsyncVoiceResourceWithStreamingResponse:
+ from .resources.voice import AsyncVoiceResourceWithStreamingResponse
+
+ return AsyncVoiceResourceWithStreamingResponse(self._client.voice)
+
+ @cached_property
+ def websearch(self) -> websearch.AsyncWebsearchResourceWithStreamingResponse:
+ from .resources.websearch import AsyncWebsearchResourceWithStreamingResponse
+
+ return AsyncWebsearchResourceWithStreamingResponse(self._client.websearch)
+
+ @cached_property
+ def worktree(self) -> worktree.AsyncWorktreeResourceWithStreamingResponse:
+ from .resources.worktree import AsyncWorktreeResourceWithStreamingResponse
+
+ return AsyncWorktreeResourceWithStreamingResponse(self._client.worktree)
+
+
+Client = Opencode
+
+AsyncClient = AsyncOpencode
diff --git a/src/opencode/_compat.py b/src/opencode/_compat.py
new file mode 100644
index 0000000..e6690a4
--- /dev/null
+++ b/src/opencode/_compat.py
@@ -0,0 +1,226 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, Union, Generic, TypeVar, Callable, cast, overload
+from datetime import date, datetime
+from typing_extensions import Self, Literal, TypedDict
+
+import pydantic
+from pydantic.fields import FieldInfo
+
+from ._types import IncEx, StrBytesIntFloat
+
+_T = TypeVar("_T")
+_ModelT = TypeVar("_ModelT", bound=pydantic.BaseModel)
+
+# --------------- Pydantic v2, v3 compatibility ---------------
+
+# Pyright incorrectly reports some of our functions as overriding a method when they don't
+# pyright: reportIncompatibleMethodOverride=false
+
+PYDANTIC_V1 = pydantic.VERSION.startswith("1.")
+
+if TYPE_CHECKING:
+
+ def parse_date(value: date | StrBytesIntFloat) -> date: # noqa: ARG001
+ ...
+
+ def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime: # noqa: ARG001
+ ...
+
+ def get_args(t: type[Any]) -> tuple[Any, ...]: # noqa: ARG001
+ ...
+
+ def is_union(tp: type[Any] | None) -> bool: # noqa: ARG001
+ ...
+
+ def get_origin(t: type[Any]) -> type[Any] | None: # noqa: ARG001
+ ...
+
+ def is_literal_type(type_: type[Any]) -> bool: # noqa: ARG001
+ ...
+
+ def is_typeddict(type_: type[Any]) -> bool: # noqa: ARG001
+ ...
+
+else:
+ # v1 re-exports
+ if PYDANTIC_V1:
+ from pydantic.typing import (
+ get_args as get_args,
+ is_union as is_union,
+ get_origin as get_origin,
+ is_typeddict as is_typeddict,
+ is_literal_type as is_literal_type,
+ )
+ from pydantic.datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime
+ else:
+ from ._utils import (
+ get_args as get_args,
+ is_union as is_union,
+ get_origin as get_origin,
+ parse_date as parse_date,
+ is_typeddict as is_typeddict,
+ parse_datetime as parse_datetime,
+ is_literal_type as is_literal_type,
+ )
+
+
+# refactored config
+if TYPE_CHECKING:
+ from pydantic import ConfigDict as ConfigDict
+else:
+ if PYDANTIC_V1:
+ # TODO: provide an error message here?
+ ConfigDict = None
+ else:
+ from pydantic import ConfigDict as ConfigDict
+
+
+# renamed methods / properties
+def parse_obj(model: type[_ModelT], value: object) -> _ModelT:
+ if PYDANTIC_V1:
+ return cast(_ModelT, model.parse_obj(value)) # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
+ else:
+ return model.model_validate(value)
+
+
+def field_is_required(field: FieldInfo) -> bool:
+ if PYDANTIC_V1:
+ return field.required # type: ignore
+ return field.is_required()
+
+
+def field_get_default(field: FieldInfo) -> Any:
+ value = field.get_default()
+ if PYDANTIC_V1:
+ return value
+ from pydantic_core import PydanticUndefined
+
+ if value == PydanticUndefined:
+ return None
+ return value
+
+
+def field_outer_type(field: FieldInfo) -> Any:
+ if PYDANTIC_V1:
+ return field.outer_type_ # type: ignore
+ return field.annotation
+
+
+def get_model_config(model: type[pydantic.BaseModel]) -> Any:
+ if PYDANTIC_V1:
+ return model.__config__ # type: ignore
+ return model.model_config
+
+
+def get_model_fields(model: type[pydantic.BaseModel]) -> dict[str, FieldInfo]:
+ if PYDANTIC_V1:
+ return model.__fields__ # type: ignore
+ return model.model_fields
+
+
+def model_copy(model: _ModelT, *, deep: bool = False) -> _ModelT:
+ if PYDANTIC_V1:
+ return model.copy(deep=deep) # type: ignore
+ return model.model_copy(deep=deep)
+
+
+def model_json(model: pydantic.BaseModel, *, indent: int | None = None) -> str:
+ if PYDANTIC_V1:
+ return model.json(indent=indent) # type: ignore
+ return model.model_dump_json(indent=indent)
+
+
+class _ModelDumpKwargs(TypedDict, total=False):
+ by_alias: bool
+
+
+def model_dump(
+ model: pydantic.BaseModel,
+ *,
+ exclude: IncEx | None = None,
+ exclude_unset: bool = False,
+ exclude_defaults: bool = False,
+ warnings: bool = True,
+ mode: Literal["json", "python"] = "python",
+ by_alias: bool | None = None,
+) -> dict[str, Any]:
+ if (not PYDANTIC_V1) or hasattr(model, "model_dump"):
+ kwargs: _ModelDumpKwargs = {}
+ if by_alias is not None:
+ kwargs["by_alias"] = by_alias
+ return model.model_dump(
+ mode=mode,
+ exclude=exclude,
+ exclude_unset=exclude_unset,
+ exclude_defaults=exclude_defaults,
+ # warnings are not supported in Pydantic v1
+ warnings=True if PYDANTIC_V1 else warnings,
+ **kwargs,
+ )
+ return cast(
+ "dict[str, Any]",
+ model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
+ exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias)
+ ),
+ )
+
+
+def model_parse(model: type[_ModelT], data: Any) -> _ModelT:
+ if PYDANTIC_V1:
+ return model.parse_obj(data) # pyright: ignore[reportDeprecated]
+ return model.model_validate(data)
+
+
+# generic models
+if TYPE_CHECKING:
+
+ class GenericModel(pydantic.BaseModel): ...
+
+else:
+ if PYDANTIC_V1:
+ import pydantic.generics
+
+ class GenericModel(pydantic.generics.GenericModel, pydantic.BaseModel): ...
+ else:
+ # there no longer needs to be a distinction in v2 but
+ # we still have to create our own subclass to avoid
+ # inconsistent MRO ordering errors
+ class GenericModel(pydantic.BaseModel): ...
+
+
+# cached properties
+if TYPE_CHECKING:
+ cached_property = property
+
+ # we define a separate type (copied from typeshed)
+ # that represents that `cached_property` is `set`able
+ # at runtime, which differs from `@property`.
+ #
+ # this is a separate type as editors likely special case
+ # `@property` and we don't want to cause issues just to have
+ # more helpful internal types.
+
+ class typed_cached_property(Generic[_T]):
+ func: Callable[[Any], _T]
+ attrname: str | None
+
+ def __init__(self, func: Callable[[Any], _T]) -> None: ...
+
+ @overload
+ def __get__(self, instance: None, owner: type[Any] | None = None) -> Self: ...
+
+ @overload
+ def __get__(self, instance: object, owner: type[Any] | None = None) -> _T: ...
+
+ def __get__(self, instance: object, owner: type[Any] | None = None) -> _T | Self:
+ raise NotImplementedError()
+
+ def __set_name__(self, owner: type[Any], name: str) -> None: ...
+
+ # __set__ is not defined at runtime, but @cached_property is designed to be settable
+ def __set__(self, instance: object, value: _T) -> None: ...
+else:
+ from functools import cached_property as cached_property
+
+ typed_cached_property = cached_property
diff --git a/src/opencode/_constants.py b/src/opencode/_constants.py
index 6e6eed3..6ddf2c7 100644
--- a/src/opencode/_constants.py
+++ b/src/opencode/_constants.py
@@ -1,27 +1,14 @@
-from __future__ import annotations
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import httpx
-DEFAULT_TIMEOUT = httpx.Timeout(timeout=600.0, connect=10.0)
-"""Ten minutes overall with a ten second connect timeout. Agent operations can run for a long time."""
+RAW_RESPONSE_HEADER = "X-Stainless-Raw-Response"
+OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to"
+# default timeout is 1 minute
+DEFAULT_TIMEOUT = httpx.Timeout(timeout=60, connect=5.0)
DEFAULT_MAX_RETRIES = 2
+DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20)
+
INITIAL_RETRY_DELAY = 0.5
MAX_RETRY_DELAY = 8.0
-MAX_RETRY_AFTER = 60.0
-"""A ``Retry-After`` above this many seconds is not honored; the request fails instead of stalling."""
-
-DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20, keepalive_expiry=30.0)
-
-DEFAULT_USERNAME = "opencode"
-"""The Basic auth user name the OpenCode server expects."""
-
-BASE_URL_ENV = "OPENCODE_BASE_URL"
-PASSWORD_ENVS = ("OPENCODE_PASSWORD", "OPENCODE_SERVER_PASSWORD")
-"""Password variables, in the order the OpenCode CLI reads them."""
-
-IDEMPOTENT_METHODS = frozenset({"GET", "HEAD", "OPTIONS", "PUT", "DELETE"})
-RETRYABLE_STATUSES = frozenset({408, 429, 500, 502, 503, 504})
-"""Statuses retried for idempotent requests."""
-RETRYABLE_STATUSES_UNSAFE = frozenset({429, 503})
-"""Statuses retried for non-idempotent requests: the server refused the request before acting on it."""
diff --git a/src/opencode/_exceptions.py b/src/opencode/_exceptions.py
index 31e9ef1..54376ee 100644
--- a/src/opencode/_exceptions.py
+++ b/src/opencode/_exceptions.py
@@ -1,271 +1,108 @@
-"""Exception hierarchy.
-
-Every error raised by the client derives from :class:`OpencodeError`::
-
- OpencodeError
- └── APIError
- ├── APIConnectionError
- │ └── APITimeoutError
- ├── APIResponseValidationError
- └── APIStatusError
- ├── BadRequestError 400
- ├── AuthenticationError 401
- ├── PermissionDeniedError 403
- ├── NotFoundError 404
- ├── ConflictError 409
- ├── ContentTooLargeError 413
- ├── UnprocessableEntityError 422
- ├── RateLimitError 429
- └── InternalServerError 5xx
- ├── NotImplementedServerError 501
- └── ServiceUnavailableError 503
-
-The server tags each declared error with a ``_tag`` such as ``"SessionNotFoundError"``. Tagged errors are raised
-as the matching subclass from :mod:`opencode._errors` (for example ``SessionNotFoundError(NotFoundError)``), so
-callers can catch a precise error or any error of that status.
-"""
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from __future__ import annotations
-from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, cast
+from typing_extensions import Literal
-import pydantic
-from typing_extensions import override
-
-if TYPE_CHECKING:
- import httpx
-
-_M = TypeVar("_M", bound=pydantic.BaseModel)
+import httpx
__all__ = [
- "APIConnectionError",
- "APIError",
- "APIResponseValidationError",
- "APIStatusError",
- "APITimeoutError",
- "AuthenticationError",
"BadRequestError",
- "ConflictError",
- "ContentTooLargeError",
- "InternalServerError",
- "NotFoundError",
- "NotImplementedServerError",
- "OpencodeError",
+ "AuthenticationError",
"PermissionDeniedError",
- "RateLimitError",
- "ServiceUnavailableError",
+ "NotFoundError",
+ "ConflictError",
"UnprocessableEntityError",
+ "RateLimitError",
+ "InternalServerError",
]
class OpencodeError(Exception):
- """Base class for every error raised by this package."""
+ pass
class APIError(OpencodeError):
- """An error that happened while talking to the OpenCode server."""
-
message: str
request: httpx.Request
- body: object | None
- """The decoded JSON error body, the raw text when it is not JSON, or ``None``."""
-
- def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None:
- super().__init__(message)
- self.message = message
- self.request = request
- self.body = body
-
-class APIConnectionError(APIError):
- """The request could not reach the server, or the connection broke before a response arrived."""
+ body: object | None
+ """The API response body.
- def __init__(self, *, request: httpx.Request, message: str = "Connection error.") -> None:
- super().__init__(message, request, body=None)
+ If the API responded with a valid JSON structure then this property will be the
+ decoded result.
+ If it isn't a valid JSON structure then this will be the raw response.
-class APITimeoutError(APIConnectionError):
- """The request timed out."""
+ If there was no response associated with this error then it will be `None`.
+ """
- def __init__(self, request: httpx.Request) -> None:
- super().__init__(request=request, message="Request timed out.")
+ def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None: # noqa: ARG002
+ super().__init__(message)
+ self.request = request
+ self.message = message
+ self.body = body
class APIResponseValidationError(APIError):
- """The server answered, but the response does not match the expected shape."""
-
response: httpx.Response
status_code: int
def __init__(self, response: httpx.Response, body: object | None, *, message: str | None = None) -> None:
- super().__init__(
- message or "The server response did not match the expected schema.", response.request, body=body
- )
+ super().__init__(message or "Data returned by API invalid for expected schema.", response.request, body=body)
self.response = response
self.status_code = response.status_code
class APIStatusError(APIError):
- """The server returned a non-success HTTP status."""
-
- status: ClassVar[int | None] = None
- """The HTTP status this class represents, or ``None`` for the generic base."""
-
- tag: ClassVar[str | None] = None
- """The server ``_tag`` this class represents, or ``None`` for status-only classes."""
+ """Raised when an API response has a status code of 4xx or 5xx."""
response: httpx.Response
status_code: int
- error_tag: str | None
- """The ``_tag`` the server sent, which can be a tag this client version does not know yet."""
def __init__(self, message: str, *, response: httpx.Response, body: object | None) -> None:
super().__init__(message, response.request, body=body)
self.response = response
self.status_code = response.status_code
- self.error_tag = _string_field(body, "_tag")
- def _typed_body(self, model: type[_M]) -> _M:
- raw: object = self.body
- body: dict[str, Any] = cast("dict[str, Any]", raw) if isinstance(raw, dict) else {}
- try:
- return model.model_validate(body)
- except pydantic.ValidationError:
- # Keep the error usable even when the body does not match this client's schema.
- return model.model_construct(**body)
- @property
- def request_id(self) -> str | None:
- """The server's request identifier, when it sends one."""
- value: str | None = self.response.headers.get("x-request-id")
- return value
+class APIConnectionError(APIError):
+ def __init__(self, *, message: str = "Connection error.", request: httpx.Request) -> None:
+ super().__init__(message, request, body=None)
- @override
- def __str__(self) -> str:
- detail = f"{self.status_code} {self.error_tag}" if self.error_tag else str(self.status_code)
- return f"{self.message} ({detail})"
+class APITimeoutError(APIConnectionError):
+ def __init__(self, request: httpx.Request) -> None:
+ super().__init__(message="Request timed out.", request=request)
-class BadRequestError(APIStatusError):
- """The server rejected the request as malformed (400)."""
- status = 400
+class BadRequestError(APIStatusError):
+ status_code: Literal[400] = 400 # pyright: ignore[reportIncompatibleVariableOverride]
class AuthenticationError(APIStatusError):
- """The password was missing or wrong (401)."""
-
- status = 401
+ status_code: Literal[401] = 401 # pyright: ignore[reportIncompatibleVariableOverride]
class PermissionDeniedError(APIStatusError):
- """The request is not allowed (403)."""
-
- status = 403
+ status_code: Literal[403] = 403 # pyright: ignore[reportIncompatibleVariableOverride]
class NotFoundError(APIStatusError):
- """The requested resource does not exist (404)."""
-
- status = 404
+ status_code: Literal[404] = 404 # pyright: ignore[reportIncompatibleVariableOverride]
class ConflictError(APIStatusError):
- """The request conflicts with the current state, for example a busy session (409)."""
-
- status = 409
- tag = "ConflictError"
-
-
-class ContentTooLargeError(APIStatusError):
- """The request body is too large (413)."""
-
- status = 413
+ status_code: Literal[409] = 409 # pyright: ignore[reportIncompatibleVariableOverride]
class UnprocessableEntityError(APIStatusError):
- """The request was well formed but could not be processed (422)."""
-
- status = 422
+ status_code: Literal[422] = 422 # pyright: ignore[reportIncompatibleVariableOverride]
class RateLimitError(APIStatusError):
- """Too many requests (429). Retried automatically, honoring ``Retry-After``."""
-
- status = 429
+ status_code: Literal[429] = 429 # pyright: ignore[reportIncompatibleVariableOverride]
class InternalServerError(APIStatusError):
- """The server failed (500, and the base class for every 5xx)."""
-
- status = 500
-
-
-class NotImplementedServerError(InternalServerError):
- """The server does not implement this endpoint (501)."""
-
- status = 501
-
-
-class ServiceUnavailableError(InternalServerError):
- """The server is temporarily unavailable (503). Retried automatically."""
-
- status = 503
- tag = "ServiceUnavailableError"
-
-
-_STATUS_ERRORS: dict[int, type[APIStatusError]] = {
- 400: BadRequestError,
- 401: AuthenticationError,
- 403: PermissionDeniedError,
- 404: NotFoundError,
- 409: ConflictError,
- 413: ContentTooLargeError,
- 422: UnprocessableEntityError,
- 429: RateLimitError,
- 500: InternalServerError,
- 501: NotImplementedServerError,
- 503: ServiceUnavailableError,
-}
-
-
-def status_error_class(
- status_code: int, tag: str | None, tagged: dict[str, type[APIStatusError]]
-) -> type[APIStatusError]:
- """Picks the most specific error class for a status code and server tag."""
- # The tag is the server's own classification, so it wins over the status code.
- if tag is not None and tag in tagged:
- return tagged[tag]
- if status_code in _STATUS_ERRORS:
- return _STATUS_ERRORS[status_code]
- if status_code >= 500:
- return InternalServerError
- return APIStatusError
-
-
-def error_message(body: object | None, status_code: int) -> str:
- """Extracts a human-readable message from an error body."""
- message = _string_field(body, "message")
- if message is None and isinstance(body, dict):
- message = _string_field(cast("dict[str, Any]", body).get("data"), "message")
- if message:
- return message
- if isinstance(body, str) and body.strip():
- return body.strip()[:500]
- return f"Request failed with status {status_code}"
-
-
-def validation_summary(error: pydantic.ValidationError, limit: int = 5) -> str:
- """Describes a validation failure by location and reason only, never echoing response values."""
- issues = error.errors(include_input=False, include_url=False)
- parts = [f"{'.'.join(str(item) for item in issue['loc']) or ''}: {issue['msg']}" for issue in issues[:limit]]
- more = f" (+{len(issues) - limit} more)" if len(issues) > limit else ""
- return f"Response did not match the expected schema: {'; '.join(parts)}{more}"
-
-
-def _string_field(body: object | None, key: str) -> str | None:
- if isinstance(body, dict):
- value: object = cast("dict[str, Any]", body).get(key)
- if isinstance(value, str):
- return value
- return None
+ pass
diff --git a/src/opencode/_files.py b/src/opencode/_files.py
new file mode 100644
index 0000000..76da9e0
--- /dev/null
+++ b/src/opencode/_files.py
@@ -0,0 +1,173 @@
+from __future__ import annotations
+
+import io
+import os
+import pathlib
+from typing import Sequence, cast, overload
+from typing_extensions import TypeVar, TypeGuard
+
+import anyio
+
+from ._types import (
+ FileTypes,
+ FileContent,
+ RequestFiles,
+ HttpxFileTypes,
+ Base64FileInput,
+ HttpxFileContent,
+ HttpxRequestFiles,
+)
+from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t
+
+_T = TypeVar("_T")
+
+
+def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]:
+ return isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike)
+
+
+def is_file_content(obj: object) -> TypeGuard[FileContent]:
+ return (
+ isinstance(obj, bytes) or isinstance(obj, tuple) or isinstance(obj, io.IOBase) or isinstance(obj, os.PathLike)
+ )
+
+
+def assert_is_file_content(obj: object, *, key: str | None = None) -> None:
+ if not is_file_content(obj):
+ prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`"
+ raise RuntimeError(
+ f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead."
+ ) from None
+
+
+@overload
+def to_httpx_files(files: None) -> None: ...
+
+
+@overload
+def to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ...
+
+
+def to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None:
+ if files is None:
+ return None
+
+ if is_mapping_t(files):
+ files = {key: _transform_file(file) for key, file in files.items()}
+ elif is_sequence_t(files):
+ files = [(key, _transform_file(file)) for key, file in files]
+ else:
+ raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence")
+
+ return files
+
+
+def _transform_file(file: FileTypes) -> HttpxFileTypes:
+ if is_file_content(file):
+ if isinstance(file, os.PathLike):
+ path = pathlib.Path(file)
+ return (path.name, path.read_bytes())
+
+ return file
+
+ if is_tuple_t(file):
+ return (file[0], read_file_content(file[1]), *file[2:])
+
+ raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")
+
+
+def read_file_content(file: FileContent) -> HttpxFileContent:
+ if isinstance(file, os.PathLike):
+ return pathlib.Path(file).read_bytes()
+ return file
+
+
+@overload
+async def async_to_httpx_files(files: None) -> None: ...
+
+
+@overload
+async def async_to_httpx_files(files: RequestFiles) -> HttpxRequestFiles: ...
+
+
+async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles | None:
+ if files is None:
+ return None
+
+ if is_mapping_t(files):
+ files = {key: await _async_transform_file(file) for key, file in files.items()}
+ elif is_sequence_t(files):
+ files = [(key, await _async_transform_file(file)) for key, file in files]
+ else:
+ raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence")
+
+ return files
+
+
+async def _async_transform_file(file: FileTypes) -> HttpxFileTypes:
+ if is_file_content(file):
+ if isinstance(file, os.PathLike):
+ path = anyio.Path(file)
+ return (path.name, await path.read_bytes())
+
+ return file
+
+ if is_tuple_t(file):
+ return (file[0], await async_read_file_content(file[1]), *file[2:])
+
+ raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")
+
+
+async def async_read_file_content(file: FileContent) -> HttpxFileContent:
+ if isinstance(file, os.PathLike):
+ return await anyio.Path(file).read_bytes()
+
+ return file
+
+
+def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T:
+ """Copy only the containers along the given paths.
+
+ Used to guard against mutation by extract_files without copying the entire structure.
+ Only dicts and lists that lie on a path are copied; everything else
+ is returned by reference.
+
+ For example, given paths=[["foo", "files", "file"]] and the structure:
+ {
+ "foo": {
+ "bar": {"baz": {}},
+ "files": {"file": }
+ }
+ }
+ The root dict, "foo", and "files" are copied (they lie on the path).
+ "bar" and "baz" are returned by reference (off the path).
+ """
+ return _deepcopy_with_paths(item, paths, 0)
+
+
+def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T:
+ if not paths:
+ return item
+ if is_mapping(item):
+ key_to_paths: dict[str, list[Sequence[str]]] = {}
+ for path in paths:
+ if index < len(path):
+ key_to_paths.setdefault(path[index], []).append(path)
+
+ # if no path continues through this mapping, it won't be mutated and copying it is redundant
+ if not key_to_paths:
+ return item
+
+ result = dict(item)
+ for key, subpaths in key_to_paths.items():
+ if key in result:
+ result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1)
+ return cast(_T, result)
+ if is_list(item):
+ array_paths = [path for path in paths if index < len(path) and path[index] == ""]
+
+ # if no path expects a list here, nothing will be mutated inside it - return by reference
+ if not array_paths:
+ return cast(_T, item)
+ return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item])
+ return item
diff --git a/src/opencode/_models.py b/src/opencode/_models.py
index a27c671..8c5ab26 100644
--- a/src/opencode/_models.py
+++ b/src/opencode/_models.py
@@ -1,54 +1,952 @@
-"""Base model for every OpenCode API type."""
-
from __future__ import annotations
-from typing import Any, ClassVar
+import os
+import inspect
+import weakref
+from typing import (
+ IO,
+ TYPE_CHECKING,
+ Any,
+ Type,
+ Union,
+ Generic,
+ TypeVar,
+ Callable,
+ Iterable,
+ Optional,
+ AsyncIterable,
+ cast,
+)
+from datetime import date, datetime
+from typing_extensions import (
+ List,
+ Unpack,
+ Literal,
+ ClassVar,
+ Protocol,
+ Required,
+ Annotated,
+ ParamSpec,
+ TypeAlias,
+ TypedDict,
+ TypeGuard,
+ final,
+ override,
+ runtime_checkable,
+)
import pydantic
-from pydantic import ConfigDict
-from typing_extensions import override
+from pydantic.fields import FieldInfo
+
+from ._types import (
+ Body,
+ IncEx,
+ Query,
+ ModelT,
+ Headers,
+ Timeout,
+ NotGiven,
+ AnyMapping,
+ HttpxRequestFiles,
+)
+from ._utils import (
+ PropertyInfo,
+ is_list,
+ is_given,
+ json_safe,
+ lru_cache,
+ is_mapping,
+ parse_date,
+ coerce_boolean,
+ parse_datetime,
+ strip_not_given,
+ extract_type_arg,
+ is_annotated_type,
+ is_type_alias_type,
+ strip_annotated_type,
+)
+from ._compat import (
+ PYDANTIC_V1,
+ ConfigDict,
+ GenericModel as BaseGenericModel,
+ get_args,
+ is_union,
+ parse_obj,
+ get_origin,
+ is_literal_type,
+ get_model_config,
+ get_model_fields,
+ field_get_default,
+)
+from ._constants import RAW_RESPONSE_HEADER
+
+if TYPE_CHECKING:
+ from pydantic import GetCoreSchemaHandler, ValidatorFunctionWrapHandler
+ from pydantic_core import CoreSchema, core_schema
+ from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema
+else:
+ try:
+ from pydantic_core import CoreSchema, core_schema
+ except ImportError:
+ CoreSchema = None
+ core_schema = None
-__all__ = ["BaseModel", "is_none"]
+__all__ = ["BaseModel", "GenericModel"]
+_T = TypeVar("_T")
+_BaseModelT = TypeVar("_BaseModelT", bound="BaseModel")
-def is_none(value: object) -> bool:
- """Field ``exclude_if`` predicate: optional fields that are not nullable are left out instead of sent as null."""
- return value is None
+P = ParamSpec("P")
+
+
+@runtime_checkable
+class _ConfigProtocol(Protocol):
+ allow_population_by_field_name: bool
class BaseModel(pydantic.BaseModel):
- """Base class for OpenCode API models.
+ if PYDANTIC_V1:
+
+ @property
+ @override
+ def model_fields_set(self) -> set[str]:
+ # a forwards-compat shim for pydantic v2
+ return self.__fields_set__ # type: ignore
+
+ class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated]
+ extra: Any = pydantic.Extra.allow # type: ignore
+ else:
+ model_config: ClassVar[ConfigDict] = ConfigDict(
+ extra="allow", defer_build=coerce_boolean(os.environ.get("DEFER_PYDANTIC_BUILD", "true"))
+ )
+
+ def to_dict(
+ self,
+ *,
+ mode: Literal["json", "python"] = "python",
+ use_api_names: bool = True,
+ exclude_unset: bool = True,
+ exclude_defaults: bool = False,
+ exclude_none: bool = False,
+ warnings: bool = True,
+ ) -> dict[str, object]:
+ """Recursively generate a dictionary representation of the model, optionally specifying which fields to include or exclude.
+
+ By default, fields that were not set by the API will not be included,
+ and keys will match the API response, *not* the property names from the model.
+
+ For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property,
+ the output will use the `"fooBar"` key (unless `use_api_names=False` is passed).
+
+ Args:
+ mode:
+ If mode is 'json', the dictionary will only contain JSON serializable types. e.g. `datetime` will be turned into a string, `"2024-3-22T18:11:19.117000Z"`.
+ If mode is 'python', the dictionary may contain any Python objects. e.g. `datetime(2024, 3, 22)`
+
+ use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`.
+ exclude_unset: Whether to exclude fields that have not been explicitly set.
+ exclude_defaults: Whether to exclude fields that are set to their default value from the output.
+ exclude_none: Whether to exclude fields that have a value of `None` from the output.
+ warnings: Whether to log warnings when invalid fields are encountered. This is only supported in Pydantic v2.
+ """
+ return self.model_dump(
+ mode=mode,
+ by_alias=use_api_names,
+ exclude_unset=exclude_unset,
+ exclude_defaults=exclude_defaults,
+ exclude_none=exclude_none,
+ warnings=warnings,
+ )
+
+ def to_json(
+ self,
+ *,
+ indent: int | None = 2,
+ use_api_names: bool = True,
+ exclude_unset: bool = True,
+ exclude_defaults: bool = False,
+ exclude_none: bool = False,
+ warnings: bool = True,
+ ) -> str:
+ """Generates a JSON string representing this model as it would be received from or sent to the API (but with indentation).
+
+ By default, fields that were not set by the API will not be included,
+ and keys will match the API response, *not* the property names from the model.
+
+ For example, if the API responds with `"fooBar": true` but we've defined a `foo_bar: bool` property,
+ the output will use the `"fooBar"` key (unless `use_api_names=False` is passed).
+
+ Args:
+ indent: Indentation to use in the JSON output. If `None` is passed, the output will be compact. Defaults to `2`
+ use_api_names: Whether to use the key that the API responded with or the property name. Defaults to `True`.
+ exclude_unset: Whether to exclude fields that have not been explicitly set.
+ exclude_defaults: Whether to exclude fields that have the default value.
+ exclude_none: Whether to exclude fields that have a value of `None`.
+ warnings: Whether to show any warnings that occurred during serialization. This is only supported in Pydantic v2.
+ """
+ return self.model_dump_json(
+ indent=indent,
+ by_alias=use_api_names,
+ exclude_unset=exclude_unset,
+ exclude_defaults=exclude_defaults,
+ exclude_none=exclude_none,
+ warnings=warnings,
+ )
+
+ @override
+ def __str__(self) -> str:
+ # mypy complains about an invalid self arg
+ return f"{self.__repr_name__()}({self.__repr_str__(', ')})" # type: ignore[misc]
+
+ # Override the 'construct' method in a way that supports recursive parsing without validation.
+ # Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836.
+ @classmethod
+ @override
+ def construct( # pyright: ignore[reportIncompatibleMethodOverride]
+ __cls: Type[ModelT],
+ _fields_set: set[str] | None = None,
+ **values: object,
+ ) -> ModelT:
+ m = __cls.__new__(__cls)
+ fields_values: dict[str, object] = {}
+
+ config = get_model_config(__cls)
+ populate_by_name = (
+ config.allow_population_by_field_name
+ if isinstance(config, _ConfigProtocol)
+ else config.get("populate_by_name")
+ )
+
+ if _fields_set is None:
+ _fields_set = set()
+
+ model_fields = get_model_fields(__cls)
+ for name, field in model_fields.items():
+ key = field.alias
+ if key is None or (key not in values and populate_by_name):
+ key = name
+
+ if key in values:
+ fields_values[name] = _construct_field(value=values[key], field=field, key=key)
+ _fields_set.add(name)
+ else:
+ fields_values[name] = field_get_default(field)
+
+ extra_field_type = _get_extra_fields_type(__cls)
+
+ _extra = {}
+ for key, value in values.items():
+ if key not in model_fields:
+ parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value
+
+ if PYDANTIC_V1:
+ _fields_set.add(key)
+ fields_values[key] = parsed
+ else:
+ _extra[key] = parsed
+
+ object.__setattr__(m, "__dict__", fields_values)
+
+ if PYDANTIC_V1:
+ # init_private_attributes() does not exist in v2
+ m._init_private_attributes() # type: ignore
+
+ # copied from Pydantic v1's `construct()` method
+ object.__setattr__(m, "__fields_set__", _fields_set)
+ else:
+ # these properties are copied from Pydantic's `model_construct()` method
+ object.__setattr__(m, "__pydantic_private__", None)
+ object.__setattr__(m, "__pydantic_extra__", _extra)
+ object.__setattr__(m, "__pydantic_fields_set__", _fields_set)
+
+ return m
+
+ if not TYPE_CHECKING:
+ # type checkers incorrectly complain about this assignment
+ # because the type signatures are technically different
+ # although not in practice
+ model_construct = construct
+
+ if PYDANTIC_V1:
+ # we define aliases for some of the new pydantic v2 methods so
+ # that we can just document these methods without having to specify
+ # a specific pydantic version as some users may not know which
+ # pydantic version they are currently using
+
+ @override
+ def model_dump(
+ self,
+ *,
+ mode: Literal["json", "python"] | str = "python",
+ include: IncEx | None = None,
+ exclude: IncEx | None = None,
+ context: Any | None = None,
+ by_alias: bool | None = None,
+ exclude_unset: bool = False,
+ exclude_defaults: bool = False,
+ exclude_none: bool = False,
+ exclude_computed_fields: bool = False,
+ round_trip: bool = False,
+ warnings: bool | Literal["none", "warn", "error"] = True,
+ fallback: Callable[[Any], Any] | None = None,
+ serialize_as_any: bool = False,
+ ) -> dict[str, Any]:
+ """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump
+
+ Generate a dictionary representation of the model, optionally specifying which fields to include or exclude.
+
+ Args:
+ mode: The mode in which `to_python` should run.
+ If mode is 'json', the output will only contain JSON serializable types.
+ If mode is 'python', the output may contain non-JSON-serializable Python objects.
+ include: A set of fields to include in the output.
+ exclude: A set of fields to exclude from the output.
+ context: Additional context to pass to the serializer.
+ by_alias: Whether to use the field's alias in the dictionary key if defined.
+ exclude_unset: Whether to exclude fields that have not been explicitly set.
+ exclude_defaults: Whether to exclude fields that are set to their default value.
+ exclude_none: Whether to exclude fields that have a value of `None`.
+ exclude_computed_fields: Whether to exclude computed fields.
+ While this can be useful for round-tripping, it is usually recommended to use the dedicated
+ `round_trip` parameter instead.
+ round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T].
+ warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors,
+ "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError].
+ fallback: A function to call when an unknown value is encountered. If not provided,
+ a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised.
+ serialize_as_any: Whether to serialize fields with duck-typing serialization behavior.
+
+ Returns:
+ A dictionary representation of the model.
+ """
+ if mode not in {"json", "python"}:
+ raise ValueError("mode must be either 'json' or 'python'")
+ if round_trip != False:
+ raise ValueError("round_trip is only supported in Pydantic v2")
+ if warnings != True:
+ raise ValueError("warnings is only supported in Pydantic v2")
+ if context is not None:
+ raise ValueError("context is only supported in Pydantic v2")
+ if serialize_as_any != False:
+ raise ValueError("serialize_as_any is only supported in Pydantic v2")
+ if fallback is not None:
+ raise ValueError("fallback is only supported in Pydantic v2")
+ if exclude_computed_fields != False:
+ raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
+ dumped = super().dict( # pyright: ignore[reportDeprecated]
+ include=include,
+ exclude=exclude,
+ by_alias=by_alias if by_alias is not None else False,
+ exclude_unset=exclude_unset,
+ exclude_defaults=exclude_defaults,
+ exclude_none=exclude_none,
+ )
+
+ return cast("dict[str, Any]", json_safe(dumped)) if mode == "json" else dumped
+
+ @override
+ def model_dump_json(
+ self,
+ *,
+ indent: int | None = None,
+ ensure_ascii: bool = False,
+ include: IncEx | None = None,
+ exclude: IncEx | None = None,
+ context: Any | None = None,
+ by_alias: bool | None = None,
+ exclude_unset: bool = False,
+ exclude_defaults: bool = False,
+ exclude_none: bool = False,
+ exclude_computed_fields: bool = False,
+ round_trip: bool = False,
+ warnings: bool | Literal["none", "warn", "error"] = True,
+ fallback: Callable[[Any], Any] | None = None,
+ serialize_as_any: bool = False,
+ ) -> str:
+ """Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump_json
+
+ Generates a JSON representation of the model using Pydantic's `to_json` method.
+
+ Args:
+ indent: Indentation to use in the JSON output. If None is passed, the output will be compact.
+ include: Field(s) to include in the JSON output. Can take either a string or set of strings.
+ exclude: Field(s) to exclude from the JSON output. Can take either a string or set of strings.
+ by_alias: Whether to serialize using field aliases.
+ exclude_unset: Whether to exclude fields that have not been explicitly set.
+ exclude_defaults: Whether to exclude fields that have the default value.
+ exclude_none: Whether to exclude fields that have a value of `None`.
+ round_trip: Whether to use serialization/deserialization between JSON and class instance.
+ warnings: Whether to show any warnings that occurred during serialization.
+
+ Returns:
+ A JSON string representation of the model.
+ """
+ if round_trip != False:
+ raise ValueError("round_trip is only supported in Pydantic v2")
+ if warnings != True:
+ raise ValueError("warnings is only supported in Pydantic v2")
+ if context is not None:
+ raise ValueError("context is only supported in Pydantic v2")
+ if serialize_as_any != False:
+ raise ValueError("serialize_as_any is only supported in Pydantic v2")
+ if fallback is not None:
+ raise ValueError("fallback is only supported in Pydantic v2")
+ if ensure_ascii != False:
+ raise ValueError("ensure_ascii is only supported in Pydantic v2")
+ if exclude_computed_fields != False:
+ raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
+ return super().json( # type: ignore[reportDeprecated]
+ indent=indent,
+ include=include,
+ exclude=exclude,
+ by_alias=by_alias if by_alias is not None else False,
+ exclude_unset=exclude_unset,
+ exclude_defaults=exclude_defaults,
+ exclude_none=exclude_none,
+ )
+
+
+class _EagerIterable(list[_T], Generic[_T]):
+ """
+ Accepts any Iterable[T] input (including generators), consumes it
+ eagerly, and validates all items upfront.
+
+ Validation preserves the original container type where possible
+ (e.g. a set[T] stays a set[T]). Serialization (model_dump / JSON)
+ always emits a list — round-tripping through model_dump() will not
+ restore the original container type.
+ """
+
+ @classmethod
+ def __get_pydantic_core_schema__(
+ cls,
+ source_type: Any,
+ handler: GetCoreSchemaHandler,
+ ) -> CoreSchema:
+ (item_type,) = get_args(source_type) or (Any,)
+ item_schema: CoreSchema = handler.generate_schema(item_type)
+ list_of_items_schema: CoreSchema = core_schema.list_schema(item_schema)
+
+ return core_schema.no_info_wrap_validator_function(
+ cls._validate,
+ list_of_items_schema,
+ serialization=core_schema.plain_serializer_function_ser_schema(
+ cls._serialize,
+ info_arg=False,
+ ),
+ )
+
+ @staticmethod
+ def _validate(v: Iterable[_T], handler: "ValidatorFunctionWrapHandler") -> Any:
+ original_type: type[Any] = type(v)
+
+ # Normalize to list so list_schema can validate each item
+ if isinstance(v, list):
+ items: list[_T] = v
+ else:
+ try:
+ items = list(v)
+ except TypeError as e:
+ raise TypeError("Value is not iterable") from e
+
+ # Validate items against the inner schema
+ validated: list[_T] = handler(items)
+
+ # Reconstruct original container type
+ if original_type is list:
+ return validated
+ # str(list) produces the list's repr, not a string built from items,
+ # so skip reconstruction for str and its subclasses.
+ if issubclass(original_type, str):
+ return validated
+ try:
+ return original_type(validated)
+ except (TypeError, ValueError):
+ # If the type cannot be reconstructed, just return the validated list
+ return validated
+
+ @staticmethod
+ def _serialize(v: Iterable[_T]) -> list[_T]:
+ """Always serialize as a list so Pydantic's JSON encoder is happy."""
+ if isinstance(v, list):
+ return v
+ return list(v)
+
+
+EagerIterable: TypeAlias = Annotated[Iterable[_T], _EagerIterable]
+
+
+def _construct_field(value: object, field: FieldInfo, key: str) -> object:
+ if value is None:
+ return field_get_default(field)
+
+ if PYDANTIC_V1:
+ type_ = cast(type, field.outer_type_) # type: ignore
+ else:
+ type_ = field.annotation # type: ignore
+
+ if type_ is None:
+ raise RuntimeError(f"Unexpected field type is None for {key}")
+
+ return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None))
+
+
+def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None:
+ if PYDANTIC_V1:
+ # TODO
+ return None
+
+ schema = cls.__pydantic_core_schema__
+ if schema["type"] == "model":
+ fields = schema["schema"]
+ if fields["type"] == "model-fields":
+ extras = fields.get("extras_schema")
+ if extras and "cls" in extras:
+ # mypy can't narrow the type
+ return extras["cls"] # type: ignore[no-any-return]
+
+ return None
+
+
+def is_basemodel(type_: type) -> bool:
+ """Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`"""
+ if is_union(type_):
+ for variant in get_args(type_):
+ if is_basemodel(variant):
+ return True
+
+ return False
+
+ return is_basemodel_type(type_)
+
+
+def is_basemodel_type(type_: type) -> TypeGuard[type[BaseModel] | type[GenericModel]]:
+ origin = get_origin(type_) or type_
+ if not inspect.isclass(origin):
+ return False
+ return issubclass(origin, BaseModel) or issubclass(origin, GenericModel)
+
+
+def build(
+ base_model_cls: Callable[P, _BaseModelT],
+ *args: P.args,
+ **kwargs: P.kwargs,
+) -> _BaseModelT:
+ """Construct a BaseModel class without validation.
+
+ This is useful for cases where you need to instantiate a `BaseModel`
+ from an API response as this provides type-safe params which isn't supported
+ by helpers like `construct_type()`.
+
+ ```py
+ build(MyModel, my_field_a="foo", my_field_b=123)
+ ```
+ """
+ if args:
+ raise TypeError(
+ "Received positional arguments which are not supported; Keyword arguments must be used instead",
+ )
+
+ return cast(_BaseModelT, construct_type(type_=base_model_cls, value=kwargs))
+
+
+def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T:
+ """Loose coercion to the expected type with construction of nested values.
+
+ Note: the returned value from this function is not guaranteed to match the
+ given type.
+ """
+ return cast(_T, construct_type(value=value, type_=type_))
+
+
+def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object:
+ """Loose coercion to the expected type with construction of nested values.
+
+ If the given value does not match the expected type then it is returned as-is.
+ """
+
+ # store a reference to the original type we were given before we extract any inner
+ # types so that we can properly resolve forward references in `TypeAliasType` annotations
+ original_type = None
+
+ # we allow `object` as the input type because otherwise, passing things like
+ # `Literal['value']` will be reported as a type error by type checkers
+ type_ = cast("type[object]", type_)
+ if is_type_alias_type(type_):
+ original_type = type_ # type: ignore[unreachable]
+ type_ = type_.__value__ # type: ignore[unreachable]
+
+ # unwrap `Annotated[T, ...]` -> `T`
+ if metadata is not None and len(metadata) > 0:
+ meta: tuple[Any, ...] = tuple(metadata)
+ elif is_annotated_type(type_):
+ meta = get_args(type_)[1:]
+ type_ = extract_type_arg(type_, 0)
+ else:
+ meta = tuple()
+
+ # we need to use the origin class for any types that are subscripted generics
+ # e.g. Dict[str, object]
+ origin = get_origin(type_) or type_
+ args = get_args(type_)
+
+ if is_union(origin):
+ try:
+ return validate_type(type_=cast("type[object]", original_type or type_), value=value)
+ except Exception:
+ pass
+
+ # if the type is a discriminated union then we want to construct the right variant
+ # in the union, even if the data doesn't match exactly, otherwise we'd break code
+ # that relies on the constructed class types, e.g.
+ #
+ # class FooType:
+ # kind: Literal['foo']
+ # value: str
+ #
+ # class BarType:
+ # kind: Literal['bar']
+ # value: int
+ #
+ # without this block, if the data we get is something like `{'kind': 'bar', 'value': 'foo'}` then
+ # we'd end up constructing `FooType` when it should be `BarType`.
+ discriminator = _build_discriminated_union_meta(union=type_, meta_annotations=meta)
+ if discriminator and is_mapping(value):
+ variant_value = value.get(discriminator.field_alias_from or discriminator.field_name)
+ if variant_value and isinstance(variant_value, str):
+ variant_type = discriminator.mapping.get(variant_value)
+ if variant_type:
+ return construct_type(type_=variant_type, value=value)
+
+ # if the data is not valid, use the first variant that doesn't fail while deserializing
+ for variant in args:
+ try:
+ return construct_type(value=value, type_=variant)
+ except Exception:
+ continue
+
+ raise RuntimeError(f"Could not convert data into a valid instance of {type_}")
+
+ if origin == dict:
+ if not is_mapping(value):
+ return value
+
+ _, items_type = get_args(type_) # Dict[_, items_type]
+ return {key: construct_type(value=item, type_=items_type) for key, item in value.items()}
+
+ if (
+ not is_literal_type(type_)
+ and inspect.isclass(origin)
+ and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel))
+ ):
+ if is_list(value):
+ return [cast(Any, type_).construct(**entry) if is_mapping(entry) else entry for entry in value]
+
+ if is_mapping(value):
+ if issubclass(type_, BaseModel):
+ return type_.construct(**value) # type: ignore[arg-type]
+
+ return cast(Any, type_).construct(**value)
+
+ if origin == list:
+ if not is_list(value):
+ return value
+
+ inner_type = args[0] # List[inner_type]
+ return [construct_type(value=entry, type_=inner_type) for entry in value]
+
+ if origin == float:
+ if isinstance(value, int):
+ coerced = float(value)
+ if coerced != value:
+ return value
+ return coerced
+
+ return value
+
+ if type_ == datetime:
+ try:
+ return parse_datetime(value) # type: ignore
+ except Exception:
+ return value
+
+ if type_ == date:
+ try:
+ return parse_date(value) # type: ignore
+ except Exception:
+ return value
+
+ return value
- Attributes use ``snake_case`` while the wire format uses OpenCode's ``camelCase`` names. Models accept either
- spelling on input and always serialize to the wire names.
- Unknown fields are kept rather than rejected, so a client keeps working against a newer server. They are
- available through ``model_extra`` and attribute access.
+@runtime_checkable
+class CachedDiscriminatorType(Protocol):
+ __discriminator__: DiscriminatorDetails
+
+
+DISCRIMINATOR_CACHE: weakref.WeakKeyDictionary[type, DiscriminatorDetails] = weakref.WeakKeyDictionary()
+
+
+class DiscriminatorDetails:
+ field_name: str
+ """The name of the discriminator field in the variant class, e.g.
+
+ ```py
+ class Foo(BaseModel):
+ type: Literal['foo']
+ ```
+
+ Will result in field_name='type'
"""
- model_config = ConfigDict(
- extra="allow",
- validate_by_name=True,
- validate_by_alias=True,
- serialize_by_alias=True,
- defer_build=True,
- protected_namespaces=(),
- use_enum_values=True,
- coerce_numbers_to_str=False,
+ field_alias_from: str | None
+ """The name of the discriminator field in the API response, e.g.
+
+ ```py
+ class Foo(BaseModel):
+ type: Literal['foo'] = Field(alias='type_from_api')
+ ```
+
+ Will result in field_alias_from='type_from_api'
+ """
+
+ mapping: dict[str, type]
+ """Mapping of discriminator value to variant type, e.g.
+
+ {'foo': FooVariant, 'bar': BarVariant}
+ """
+
+ def __init__(
+ self,
+ *,
+ mapping: dict[str, type],
+ discriminator_field: str,
+ discriminator_alias: str | None,
+ ) -> None:
+ self.mapping = mapping
+ self.field_name = discriminator_field
+ self.field_alias_from = discriminator_alias
+
+
+def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None:
+ cached = DISCRIMINATOR_CACHE.get(union)
+ if cached is not None:
+ return cached
+
+ discriminator_field_name: str | None = None
+
+ for annotation in meta_annotations:
+ if isinstance(annotation, PropertyInfo) and annotation.discriminator is not None:
+ discriminator_field_name = annotation.discriminator
+ break
+
+ if not discriminator_field_name:
+ return None
+
+ mapping: dict[str, type] = {}
+ discriminator_alias: str | None = None
+
+ for variant in get_args(union):
+ variant = strip_annotated_type(variant)
+ if is_basemodel_type(variant):
+ if PYDANTIC_V1:
+ field_info = cast("dict[str, FieldInfo]", variant.__fields__).get(discriminator_field_name) # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
+ if not field_info:
+ continue
+
+ # Note: if one variant defines an alias then they all should
+ discriminator_alias = field_info.alias
+
+ if (annotation := getattr(field_info, "annotation", None)) and is_literal_type(annotation):
+ for entry in get_args(annotation):
+ if isinstance(entry, str):
+ mapping[entry] = variant
+ else:
+ field = _extract_field_schema_pv2(variant, discriminator_field_name)
+ if not field:
+ continue
+
+ # Note: if one variant defines an alias then they all should
+ discriminator_alias = field.get("serialization_alias")
+
+ field_schema = field["schema"]
+
+ if field_schema["type"] == "literal":
+ for entry in cast("LiteralSchema", field_schema)["expected"]:
+ if isinstance(entry, str):
+ mapping[entry] = variant
+
+ if not mapping:
+ return None
+
+ details = DiscriminatorDetails(
+ mapping=mapping,
+ discriminator_field=discriminator_field_name,
+ discriminator_alias=discriminator_alias,
)
+ DISCRIMINATOR_CACHE.setdefault(union, details)
+ return details
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ()
- """Fields serialized even when left at their default, such as a union member's ``type`` tag."""
- @override
- def model_post_init(self, context: Any, /) -> None:
- if self.__opencode_always_set__:
- self.__pydantic_fields_set__.update(self.__opencode_always_set__)
+def _extract_field_schema_pv2(model: type[BaseModel], field_name: str) -> ModelField | None:
+ schema = model.__pydantic_core_schema__
+ if schema["type"] == "definitions":
+ schema = schema["schema"]
+
+ if schema["type"] != "model":
+ return None
+
+ schema = cast("ModelSchema", schema)
+ fields_schema = schema["schema"]
+ if fields_schema["type"] != "model-fields":
+ return None
+
+ fields_schema = cast("ModelFieldsSchema", fields_schema)
+ field = fields_schema["fields"].get(field_name)
+ if not field:
+ return None
+
+ return cast("ModelField", field) # pyright: ignore[reportUnnecessaryCast]
+
+
+def validate_type(*, type_: type[_T], value: object) -> _T:
+ """Strict validation that the given value matches the expected type"""
+ if inspect.isclass(type_) and issubclass(type_, pydantic.BaseModel):
+ return cast(_T, parse_obj(type_, value))
+
+ return cast(_T, _validate_non_model_type(type_=type_, value=value))
+
+
+def set_pydantic_config(typ: Any, config: pydantic.ConfigDict) -> None:
+ """Add a pydantic config for the given type.
+
+ Note: this is a no-op on Pydantic v1.
+ """
+ setattr(typ, "__pydantic_config__", config) # noqa: B010
+
+
+# our use of subclassing here causes weirdness for type checkers,
+# so we just pretend that we don't subclass
+if TYPE_CHECKING:
+ GenericModel = BaseModel
+else:
+
+ class GenericModel(BaseGenericModel, BaseModel):
+ pass
+
+
+if not PYDANTIC_V1:
+ from pydantic import TypeAdapter as _TypeAdapter
+
+ _CachedTypeAdapter = cast("TypeAdapter[object]", lru_cache(maxsize=None)(_TypeAdapter))
+
+ if TYPE_CHECKING:
+ from pydantic import TypeAdapter
+ else:
+ TypeAdapter = _CachedTypeAdapter
+
+ def _validate_non_model_type(*, type_: type[_T], value: object) -> _T:
+ return TypeAdapter(type_).validate_python(value)
+
+elif not TYPE_CHECKING: # TODO: condition is weird
+
+ class RootModel(GenericModel, Generic[_T]):
+ """Used as a placeholder to easily convert runtime types to a Pydantic format
+ to provide validation.
+
+ For example:
+ ```py
+ validated = RootModel[int](__root__="5").__root__
+ # validated: 5
+ ```
+ """
+
+ __root__: _T
+
+ def _validate_non_model_type(*, type_: type[_T], value: object) -> _T:
+ model = _create_pydantic_model(type_).validate(value)
+ return cast(_T, model.__root__)
+
+ def _create_pydantic_model(type_: _T) -> Type[RootModel[_T]]:
+ return RootModel[type_] # type: ignore
+
+
+class FinalRequestOptionsInput(TypedDict, total=False):
+ method: Required[str]
+ url: Required[str]
+ params: Query
+ headers: Headers
+ max_retries: int
+ timeout: float | Timeout | None
+ files: HttpxRequestFiles | None
+ idempotency_key: str
+ content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None]
+ json_data: Body
+ extra_json: AnyMapping
+ follow_redirects: bool
+
+
+@final
+class FinalRequestOptions(pydantic.BaseModel):
+ method: str
+ url: str
+ params: Query = {}
+ headers: Union[Headers, NotGiven] = NotGiven()
+ max_retries: Union[int, NotGiven] = NotGiven()
+ timeout: Union[float, Timeout, None, NotGiven] = NotGiven()
+ files: Union[HttpxRequestFiles, None] = None
+ idempotency_key: Union[str, None] = None
+ post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven()
+ follow_redirects: Union[bool, None] = None
+
+ content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None
+ # It should be noted that we cannot use `json` here as that would override
+ # a BaseModel method in an incompatible fashion.
+ json_data: Union[Body, None] = None
+ extra_json: Union[AnyMapping, None] = None
+
+ if PYDANTIC_V1:
+
+ class Config(pydantic.BaseConfig): # pyright: ignore[reportDeprecated]
+ arbitrary_types_allowed: bool = True
+ else:
+ model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True)
+
+ def get_max_retries(self, max_retries: int) -> int:
+ if isinstance(self.max_retries, NotGiven):
+ return max_retries
+ return self.max_retries
+
+ def _strip_raw_response_header(self) -> None:
+ if not is_given(self.headers):
+ return
+
+ if self.headers.get(RAW_RESPONSE_HEADER):
+ self.headers = {**self.headers}
+ self.headers.pop(RAW_RESPONSE_HEADER)
- def to_dict(self, *, exclude_unset: bool = True) -> dict[str, Any]:
- """Returns the model as a JSON-compatible dict using wire field names."""
- return self.model_dump(mode="json", by_alias=True, exclude_unset=exclude_unset)
+ # override the `construct` method so that we can run custom transformations.
+ # this is necessary as we don't want to do any actual runtime type checking
+ # (which means we can't use validators) but we do want to ensure that `NotGiven`
+ # values are not present
+ #
+ # type ignore required because we're adding explicit types to `**values`
+ @classmethod
+ def construct( # type: ignore
+ cls,
+ _fields_set: set[str] | None = None,
+ **values: Unpack[FinalRequestOptionsInput],
+ ) -> FinalRequestOptions:
+ kwargs: dict[str, Any] = {
+ # we unconditionally call `strip_not_given` on any value
+ # as it will just ignore any non-mapping types
+ key: strip_not_given(value)
+ for key, value in values.items()
+ }
+ if PYDANTIC_V1:
+ return cast(FinalRequestOptions, super().construct(_fields_set, **kwargs)) # pyright: ignore[reportDeprecated]
+ return super().model_construct(_fields_set, **kwargs)
- def to_json(self, *, indent: int | None = None, exclude_unset: bool = True) -> str:
- """Returns the model as JSON text using wire field names."""
- return self.model_dump_json(by_alias=True, exclude_unset=exclude_unset, indent=indent)
+ if not TYPE_CHECKING:
+ # type checkers incorrectly complain about this assignment
+ model_construct = construct
diff --git a/src/opencode/_qs.py b/src/opencode/_qs.py
index 19e4714..4127c19 100644
--- a/src/opencode/_qs.py
+++ b/src/opencode/_qs.py
@@ -1,95 +1,149 @@
-"""Request encoding: query strings, path segments and JSON bodies.
-
-The rules match the official TypeScript client so both send byte-identical requests:
-
-- ``None`` becomes the string ``"null"``; ``NOT_GIVEN`` leaves the parameter out;
-- booleans become ``"true"`` / ``"false"``;
-- lists repeat the key once per item;
-- objects use deep-object style, ``location[directory]=/repo``.
-"""
-
from __future__ import annotations
-import base64
-import datetime as dt
-import enum
-from collections.abc import Mapping, Sequence
-from typing import Any, cast
-from urllib.parse import quote
-
-import pydantic
-
-from ._types import NotGiven
-
-__all__ = ["encode_path", "encode_path_segment", "encode_query", "to_jsonable"]
-
-
-def encode_query(params: Mapping[str, object]) -> list[tuple[str, str]]:
- """Flattens query parameters into ordered ``(key, value)`` pairs."""
- pairs: list[tuple[str, str]] = []
- for key, value in params.items():
- _append(pairs, key, to_jsonable(value))
- return pairs
-
-
-def _append(pairs: list[tuple[str, str]], key: str, value: object) -> None:
- if isinstance(value, NotGiven):
- return
- if value is None:
- pairs.append((key, "null"))
- elif isinstance(value, bool):
- pairs.append((key, "true" if value else "false"))
- elif isinstance(value, (list, tuple)):
- for item in cast("Sequence[object]", value):
- _append(pairs, key, item)
- elif isinstance(value, dict):
- for child, item in cast("dict[str, object]", value).items():
- _append(pairs, f"{key}[{child}]", item)
- else:
- pairs.append((key, str(value)))
-
-
-def encode_path_segment(value: object) -> str:
- """Encodes one path parameter, escaping ``/`` and every other reserved character.
-
- ``.`` and ``..`` are percent-encoded too, so URL normalization cannot turn a parameter into a different route.
- """
- text = str(value)
- if text == "":
- raise ValueError("Path parameters must not be empty")
- return _encode_part(text)
-
-
-def encode_path(value: str) -> str:
- """Encodes a trailing wildcard path, keeping ``/`` separators and encoding dot segments."""
- return "/".join(_encode_part(part) for part in value.split("/"))
-
-
-def _encode_part(part: str) -> str:
- if part in (".", ".."):
- return part.replace(".", "%2E")
- return quote(part, safe="")
-
-
-def to_jsonable(value: Any) -> Any:
- """Converts models and standard containers to plain JSON values, dropping ``NOT_GIVEN`` entries."""
- if isinstance(value, pydantic.BaseModel):
- return value.model_dump(mode="json", by_alias=True, exclude_unset=True)
- if isinstance(value, NotGiven) or value is None or isinstance(value, (str, int, float, bool)):
- return value
- if isinstance(value, Mapping):
- mapping = cast("Mapping[object, object]", value)
- return {str(key): to_jsonable(item) for key, item in mapping.items() if not isinstance(item, NotGiven)}
- if isinstance(value, (bytes, bytearray, memoryview)):
- return base64.b64encode(bytes(cast("bytes", value))).decode("ascii")
- if isinstance(value, Sequence):
- return [to_jsonable(item) for item in cast("Sequence[object]", value)]
- if isinstance(value, (set, frozenset)):
- return [to_jsonable(item) for item in cast("set[object]", value)]
- if isinstance(value, enum.Enum):
- return to_jsonable(value.value)
- if isinstance(value, (dt.datetime, dt.date)):
- return value.isoformat()
- value_type: Any = value.__class__
- adapter: pydantic.TypeAdapter[Any] = pydantic.TypeAdapter(value_type)
- return adapter.dump_python(value, mode="json", by_alias=True)
+from typing import Any, List, Tuple, Union, Mapping, TypeVar
+from urllib.parse import parse_qs, urlencode
+from typing_extensions import get_args
+
+from ._types import NotGiven, ArrayFormat, NestedFormat, not_given
+from ._utils import flatten
+
+_T = TypeVar("_T")
+
+PrimitiveData = Union[str, int, float, bool, None]
+# this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"]
+# https://github.com/microsoft/pyright/issues/3555
+Data = Union[PrimitiveData, List[Any], Tuple[Any], "Mapping[str, Any]"]
+Params = Mapping[str, Data]
+
+
+class Querystring:
+ array_format: ArrayFormat
+ nested_format: NestedFormat
+
+ def __init__(
+ self,
+ *,
+ array_format: ArrayFormat = "repeat",
+ nested_format: NestedFormat = "brackets",
+ ) -> None:
+ self.array_format = array_format
+ self.nested_format = nested_format
+
+ def parse(self, query: str) -> Mapping[str, object]:
+ # Note: custom format syntax is not supported yet
+ return parse_qs(query)
+
+ def stringify(
+ self,
+ params: Params,
+ *,
+ array_format: ArrayFormat | NotGiven = not_given,
+ nested_format: NestedFormat | NotGiven = not_given,
+ ) -> str:
+ return urlencode(
+ self.stringify_items(
+ params,
+ array_format=array_format,
+ nested_format=nested_format,
+ )
+ )
+
+ def stringify_items(
+ self,
+ params: Params,
+ *,
+ array_format: ArrayFormat | NotGiven = not_given,
+ nested_format: NestedFormat | NotGiven = not_given,
+ ) -> list[tuple[str, str]]:
+ opts = Options(
+ qs=self,
+ array_format=array_format,
+ nested_format=nested_format,
+ )
+ return flatten([self._stringify_item(key, value, opts) for key, value in params.items()])
+
+ def _stringify_item(
+ self,
+ key: str,
+ value: Data,
+ opts: Options,
+ ) -> list[tuple[str, str]]:
+ if isinstance(value, Mapping):
+ items: list[tuple[str, str]] = []
+ nested_format = opts.nested_format
+ for subkey, subvalue in value.items():
+ items.extend(
+ self._stringify_item(
+ # TODO: error if unknown format
+ f"{key}.{subkey}" if nested_format == "dots" else f"{key}[{subkey}]",
+ subvalue,
+ opts,
+ )
+ )
+ return items
+
+ if isinstance(value, (list, tuple)):
+ array_format = opts.array_format
+ if array_format == "comma":
+ return [
+ (
+ key,
+ ",".join(self._primitive_value_to_str(item) for item in value if item is not None),
+ ),
+ ]
+ elif array_format == "repeat":
+ items = []
+ for item in value:
+ items.extend(self._stringify_item(key, item, opts))
+ return items
+ elif array_format == "indices":
+ items = []
+ for i, item in enumerate(value):
+ items.extend(self._stringify_item(f"{key}[{i}]", item, opts))
+ return items
+ elif array_format == "brackets":
+ items = []
+ key = key + "[]"
+ for item in value:
+ items.extend(self._stringify_item(key, item, opts))
+ return items
+ else:
+ raise NotImplementedError(
+ f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}"
+ )
+
+ serialised = self._primitive_value_to_str(value)
+ if not serialised:
+ return []
+ return [(key, serialised)]
+
+ def _primitive_value_to_str(self, value: PrimitiveData) -> str:
+ # copied from httpx
+ if value is True:
+ return "true"
+ elif value is False:
+ return "false"
+ elif value is None:
+ return ""
+ return str(value)
+
+
+_qs = Querystring()
+parse = _qs.parse
+stringify = _qs.stringify
+stringify_items = _qs.stringify_items
+
+
+class Options:
+ array_format: ArrayFormat
+ nested_format: NestedFormat
+
+ def __init__(
+ self,
+ qs: Querystring = _qs,
+ *,
+ array_format: ArrayFormat | NotGiven = not_given,
+ nested_format: NestedFormat | NotGiven = not_given,
+ ) -> None:
+ self.array_format = qs.array_format if isinstance(array_format, NotGiven) else array_format
+ self.nested_format = qs.nested_format if isinstance(nested_format, NotGiven) else nested_format
diff --git a/src/opencode/_resource.py b/src/opencode/_resource.py
index 9b75af2..80802c9 100644
--- a/src/opencode/_resource.py
+++ b/src/opencode/_resource.py
@@ -1,42 +1,43 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
from __future__ import annotations
+import time
from typing import TYPE_CHECKING
-from ._types import NotGiven
+import anyio
if TYPE_CHECKING:
- from ._client import AsyncOpencode, Opencode
-
-__all__ = ["AsyncAPIResource", "SyncAPIResource"]
+ from ._client import Opencode, AsyncOpencode
class SyncAPIResource:
- """Base class for generated resource groups of the sync client."""
-
_client: Opencode
def __init__(self, client: Opencode) -> None:
self._client = client
+ self._get = client.get
+ self._post = client.post
+ self._patch = client.patch
+ self._put = client.put
+ self._delete = client.delete
+ self._get_api_list = client.get_api_list
- def _location(self, location: object) -> object:
- """Falls back to the client's ``directory`` for endpoints that take a ``location``."""
- return _default_location(location, self._client.directory)
+ def _sleep(self, seconds: float) -> None:
+ time.sleep(seconds)
class AsyncAPIResource:
- """Base class for generated resource groups of the async client."""
-
_client: AsyncOpencode
def __init__(self, client: AsyncOpencode) -> None:
self._client = client
-
- def _location(self, location: object) -> object:
- """Falls back to the client's ``directory`` for endpoints that take a ``location``."""
- return _default_location(location, self._client.directory)
-
-
-def _default_location(location: object, directory: str | None) -> object:
- if isinstance(location, NotGiven) and directory is not None:
- return {"directory": directory}
- return location
+ self._get = client.get
+ self._post = client.post
+ self._patch = client.patch
+ self._put = client.put
+ self._delete = client.delete
+ self._get_api_list = client.get_api_list
+
+ async def _sleep(self, seconds: float) -> None:
+ await anyio.sleep(seconds)
diff --git a/src/opencode/_response.py b/src/opencode/_response.py
index df07601..92a6439 100644
--- a/src/opencode/_response.py
+++ b/src/opencode/_response.py
@@ -1,316 +1,833 @@
-"""Raw HTTP response access for generated methods.
-
-``client.session.with_raw_response.get(...)`` calls the same method, but returns an :class:`APIResponse` with the
-status, headers and request instead of the parsed value; :meth:`APIResponse.parse` gives the value the plain call
-would have returned. ``with_streaming_response`` does the same without reading the body, inside a context manager::
-
- with client.file.with_streaming_response.content(...) as response:
- for chunk in response.iter_bytes():
- ...
-
-The generated methods are unchanged: a context variable tells the transport to hand back the response instead of
-parsing it.
-"""
-
from __future__ import annotations
-import contextvars
+import os
+import inspect
+import logging
+import datetime
import functools
-import json
-from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar, cast, overload
-
-from typing_extensions import ParamSpec, override
+from types import TracebackType
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Union,
+ Generic,
+ TypeVar,
+ Callable,
+ Iterator,
+ AsyncIterator,
+ cast,
+ overload,
+)
+from typing_extensions import Awaitable, ParamSpec, override, get_origin
+
+import anyio
+import httpx
+import pydantic
+
+from ._types import NoneType
+from ._utils import is_given, extract_type_arg, is_annotated_type, is_type_alias_type, extract_type_var_from_base
+from ._models import BaseModel, is_basemodel
+from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER
+from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type
+from ._exceptions import OpencodeError, APIResponseValidationError
if TYPE_CHECKING:
- from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
- from types import TracebackType
+ from ._models import FinalRequestOptions
+ from ._base_client import BaseClient
- import httpx
- from ._pagination import AsyncCursorPage, AsyncPaginator
-
-__all__ = [
- "APIResponse",
- "AsyncAPIResponse",
- "AsyncResponseContextManager",
- "ResponseContextManager",
-]
-
-_P = ParamSpec("_P")
-_R = TypeVar("_R")
+P = ParamSpec("P")
+R = TypeVar("R")
_T = TypeVar("_T")
+_APIResponseT = TypeVar("_APIResponseT", bound="APIResponse[Any]")
+_AsyncAPIResponseT = TypeVar("_AsyncAPIResponseT", bound="AsyncAPIResponse[Any]")
-RawMode = Literal["raw", "stream"]
-
-_raw_mode: contextvars.ContextVar[RawMode | None] = contextvars.ContextVar("opencode_raw_mode", default=None)
-
+log: logging.Logger = logging.getLogger(__name__)
-def raw_mode() -> RawMode | None:
- """The raw-response mode of the current call, or ``None`` for a normal call."""
- return _raw_mode.get()
+class BaseAPIResponse(Generic[R]):
+ _cast_to: type[R]
+ _client: BaseClient[Any, Any]
+ _parsed_by_type: dict[type[Any], Any]
+ _is_sse_stream: bool
+ _stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None
+ _options: FinalRequestOptions
-class _BaseAPIResponse(Generic[_R]):
http_response: httpx.Response
- """The underlying ``httpx.Response``."""
+
+ retries_taken: int
+ """The number of retries made. If no retries happened this will be `0`"""
def __init__(
- self, response: httpx.Response, parser: Callable[[httpx.Response], Any], *, event_stream: bool
+ self,
+ *,
+ raw: httpx.Response,
+ cast_to: type[R],
+ client: BaseClient[Any, Any],
+ stream: bool,
+ stream_cls: type[Stream[Any]] | type[AsyncStream[Any]] | None,
+ options: FinalRequestOptions,
+ retries_taken: int = 0,
) -> None:
- self.http_response = response
- self._parser = parser
- self._event_stream = event_stream
- self._parsed: Any = _UNSET
+ self._cast_to = cast_to
+ self._client = client
+ self._parsed_by_type = {}
+ self._is_sse_stream = stream
+ self._stream_cls = stream_cls
+ self._options = options
+ self.http_response = raw
+ self.retries_taken = retries_taken
@property
def headers(self) -> httpx.Headers:
- """Response headers."""
return self.http_response.headers
@property
- def status_code(self) -> int:
- """HTTP status code."""
- return self.http_response.status_code
+ def http_request(self) -> httpx.Request:
+ """Returns the httpx Request instance associated with the current response."""
+ return self.http_response.request
@property
- def request(self) -> httpx.Request:
- """The request that was sent (the last attempt, when it was retried)."""
- return self.http_response.request
+ def status_code(self) -> int:
+ return self.http_response.status_code
@property
def url(self) -> httpx.URL:
- """The request URL."""
+ """Returns the URL for which the request was made."""
return self.http_response.url
@property
def method(self) -> str:
- """The request method."""
- return self.http_response.request.method
+ return self.http_request.method
+
+ @property
+ def http_version(self) -> str:
+ return self.http_response.http_version
@property
- def elapsed(self) -> Any:
- """Time from sending the request to receiving the response headers."""
+ def elapsed(self) -> datetime.timedelta:
+ """The time taken for the complete request/response cycle to complete."""
return self.http_response.elapsed
@property
def is_closed(self) -> bool:
- """Whether the connection has been released."""
+ """Whether or not the response body has been closed.
+
+ If this is False then there is response data that has not been read yet.
+ You must either fully consume the response body or call `.close()`
+ before discarding the response to prevent resource leaks.
+ """
return self.http_response.is_closed
@override
def __repr__(self) -> str:
- return f"<{type(self).__name__} [{self.status_code} {self.http_response.reason_phrase}]>"
-
-
-class _Unset:
- pass
-
-
-_UNSET = _Unset()
-
+ return (
+ f"<{self.__class__.__name__} [{self.status_code} {self.http_response.reason_phrase}] type={self._cast_to}>"
+ )
+
+ def _parse(self, *, to: type[_T] | None = None) -> R | _T:
+ cast_to = to if to is not None else self._cast_to
+
+ # unwrap `TypeAlias('Name', T)` -> `T`
+ if is_type_alias_type(cast_to):
+ cast_to = cast_to.__value__ # type: ignore[unreachable]
+
+ # unwrap `Annotated[T, ...]` -> `T`
+ if cast_to and is_annotated_type(cast_to):
+ cast_to = extract_type_arg(cast_to, 0)
+
+ origin = get_origin(cast_to) or cast_to
+
+ if self._is_sse_stream:
+ if to:
+ if not is_stream_class_type(to):
+ raise TypeError(f"Expected custom parse type to be a subclass of {Stream} or {AsyncStream}")
+
+ return cast(
+ _T,
+ to(
+ cast_to=extract_stream_chunk_type(
+ to,
+ failure_message="Expected custom stream type to be passed with a type argument, e.g. Stream[ChunkType]",
+ ),
+ response=self.http_response,
+ client=cast(Any, self._client),
+ options=self._options,
+ ),
+ )
+
+ if self._stream_cls:
+ return cast(
+ R,
+ self._stream_cls(
+ cast_to=extract_stream_chunk_type(self._stream_cls),
+ response=self.http_response,
+ client=cast(Any, self._client),
+ options=self._options,
+ ),
+ )
+
+ stream_cls = cast("type[Stream[Any]] | type[AsyncStream[Any]] | None", self._client._default_stream_cls)
+ if stream_cls is None:
+ raise MissingStreamClassError()
+
+ return cast(
+ R,
+ stream_cls(
+ cast_to=cast_to,
+ response=self.http_response,
+ client=cast(Any, self._client),
+ options=self._options,
+ ),
+ )
+
+ if cast_to is NoneType:
+ return cast(R, None)
+
+ response = self.http_response
+ if cast_to == str:
+ return cast(R, response.text)
+
+ if cast_to == bytes:
+ return cast(R, response.content)
+
+ if cast_to == int:
+ return cast(R, int(response.text))
+
+ if cast_to == float:
+ return cast(R, float(response.text))
+
+ if cast_to == bool:
+ return cast(R, response.text.lower() == "true")
+
+ if origin == APIResponse:
+ raise RuntimeError("Unexpected state - cast_to is `APIResponse`")
+
+ if inspect.isclass(origin) and issubclass(origin, httpx.Response):
+ # Because of the invariance of our ResponseT TypeVar, users can subclass httpx.Response
+ # and pass that class to our request functions. We cannot change the variance to be either
+ # covariant or contravariant as that makes our usage of ResponseT illegal. We could construct
+ # the response class ourselves but that is something that should be supported directly in httpx
+ # as it would be easy to incorrectly construct the Response object due to the multitude of arguments.
+ if cast_to != httpx.Response:
+ raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`")
+ return cast(R, response)
+
+ if (
+ inspect.isclass(
+ origin # pyright: ignore[reportUnknownArgumentType]
+ )
+ and not issubclass(origin, BaseModel)
+ and issubclass(origin, pydantic.BaseModel)
+ ):
+ raise TypeError("Pydantic models must subclass our base model type, e.g. `from opencode import BaseModel`")
+
+ if (
+ cast_to is not object
+ and not origin is list
+ and not origin is dict
+ and not origin is Union
+ and not issubclass(origin, BaseModel)
+ ):
+ raise RuntimeError(
+ f"Unsupported type, expected {cast_to} to be a subclass of {BaseModel}, {dict}, {list}, {Union}, {NoneType}, {str} or {httpx.Response}."
+ )
+
+ # split is required to handle cases where additional information is included
+ # in the response, e.g. application/json; charset=utf-8
+ content_type, *_ = response.headers.get("content-type", "*").split(";")
+ if not content_type.endswith("json"):
+ if is_basemodel(cast_to):
+ try:
+ data = response.json()
+ except Exception as exc:
+ log.debug("Could not read JSON from response data due to %s - %s", type(exc), exc)
+ else:
+ return self._client._process_response_data(
+ data=data,
+ cast_to=cast_to, # type: ignore
+ response=response,
+ )
+
+ if self._client._strict_response_validation:
+ raise APIResponseValidationError(
+ response=response,
+ message=f"Expected Content-Type response header to be `application/json` but received `{content_type}` instead.",
+ body=response.text,
+ )
+
+ # If the API responds with content that isn't JSON then we just return
+ # the (decoded) text without performing any parsing so that you can still
+ # handle the response however you need to.
+ return response.text # type: ignore
+
+ data = response.json()
+
+ return self._client._process_response_data(
+ data=data,
+ cast_to=cast_to, # type: ignore
+ response=response,
+ )
+
+
+class APIResponse(BaseAPIResponse[R]):
+ @overload
+ def parse(self, *, to: type[_T]) -> _T: ...
+
+ @overload
+ def parse(self) -> R: ...
+
+ def parse(self, *, to: type[_T] | None = None) -> R | _T:
+ """Returns the rich python representation of this response's data.
+
+ For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`.
+
+ You can customise the type that the response is parsed into through
+ the `to` argument, e.g.
+
+ ```py
+ from opencode import BaseModel
+
+
+ class MyModel(BaseModel):
+ foo: str
+
+
+ obj = response.parse(to=MyModel)
+ print(obj.foo)
+ ```
+
+ We support parsing:
+ - `BaseModel`
+ - `dict`
+ - `list`
+ - `Union`
+ - `str`
+ - `int`
+ - `float`
+ - `httpx.Response`
+ """
+ cache_key = to if to is not None else self._cast_to
+ cached = self._parsed_by_type.get(cache_key)
+ if cached is not None:
+ return cached # type: ignore[no-any-return]
-class APIResponse(_BaseAPIResponse[_R]):
- """An HTTP response from a sync ``with_raw_response`` / ``with_streaming_response`` call."""
+ if not self._is_sse_stream:
+ self.read()
- def parse(self) -> _R:
- """Returns the value the plain method call would have returned, reading the body first if needed.
+ parsed = self._parse(to=to)
+ if is_given(self._options.post_parser):
+ parsed = self._options.post_parser(parsed)
- For event-stream endpoints this is the :class:`~opencode.Stream`, which consumes the body as you iterate.
- """
- if isinstance(self._parsed, _Unset):
- if not self._event_stream:
- self.read()
- self._parsed = self._parser(self.http_response)
- return cast("_R", self._parsed)
+ self._parsed_by_type[cache_key] = parsed
+ return parsed
def read(self) -> bytes:
- """Reads and returns the whole body."""
- return self.http_response.read()
+ """Read and return the binary response content."""
+ try:
+ return self.http_response.read()
+ except httpx.StreamConsumed as exc:
+ # The default error raised by httpx isn't very
+ # helpful in our case so we re-raise it with
+ # a different error message.
+ raise StreamAlreadyConsumed() from exc
- @property
- def content(self) -> bytes:
- """The whole body, read on first access."""
- return self.read()
-
- @property
def text(self) -> str:
- """The body decoded as text."""
+ """Read and decode the response content into a string."""
self.read()
return self.http_response.text
- def json(self) -> Any:
- """The body decoded as JSON."""
- return json.loads(self.read())
+ def json(self) -> object:
+ """Read and decode the JSON response content."""
+ self.read()
+ return self.http_response.json()
+
+ def close(self) -> None:
+ """Close the response and release the connection.
+
+ Automatically called if the response body is read to completion.
+ """
+ self.http_response.close()
def iter_bytes(self, chunk_size: int | None = None) -> Iterator[bytes]:
- """Iterates over the body in byte chunks, decoding any content encoding."""
- yield from self.http_response.iter_bytes(chunk_size)
+ """
+ A byte-iterator over the decoded response content.
+
+ This automatically handles gzip, deflate and brotli encoded responses.
+ """
+ for chunk in self.http_response.iter_bytes(chunk_size):
+ yield chunk
def iter_text(self, chunk_size: int | None = None) -> Iterator[str]:
- """Iterates over the body as decoded text chunks."""
- yield from self.http_response.iter_text(chunk_size)
+ """A str-iterator over the decoded response content
+ that handles both gzip, deflate, etc but also detects the content's
+ string encoding.
+ """
+ for chunk in self.http_response.iter_text(chunk_size):
+ yield chunk
def iter_lines(self) -> Iterator[str]:
- """Iterates over the body line by line."""
- yield from self.http_response.iter_lines()
+ """Like `iter_text()` but will only yield chunks for each line"""
+ for chunk in self.http_response.iter_lines():
+ yield chunk
+
+
+class AsyncAPIResponse(BaseAPIResponse[R]):
+ @overload
+ async def parse(self, *, to: type[_T]) -> _T: ...
+
+ @overload
+ async def parse(self) -> R: ...
+
+ async def parse(self, *, to: type[_T] | None = None) -> R | _T:
+ """Returns the rich python representation of this response's data.
+
+ For lower-level control, see `.read()`, `.json()`, `.iter_bytes()`.
+
+ You can customise the type that the response is parsed into through
+ the `to` argument, e.g.
+
+ ```py
+ from opencode import BaseModel
- def close(self) -> None:
- """Releases the connection without reading the rest of the body."""
- self.http_response.close()
+ class MyModel(BaseModel):
+ foo: str
-class AsyncAPIResponse(_BaseAPIResponse[_R]):
- """An HTTP response from an async ``with_raw_response`` / ``with_streaming_response`` call."""
- async def parse(self) -> _R:
- """Returns the value the plain method call would have returned, reading the body first if needed.
+ obj = response.parse(to=MyModel)
+ print(obj.foo)
+ ```
- For event-stream endpoints this is the :class:`~opencode.AsyncStream`, which consumes the body as you
- iterate.
+ We support parsing:
+ - `BaseModel`
+ - `dict`
+ - `list`
+ - `Union`
+ - `str`
+ - `httpx.Response`
"""
- if isinstance(self._parsed, _Unset):
- if not self._event_stream:
- await self.read()
- self._parsed = self._parser(self.http_response)
- return cast("_R", self._parsed)
+ cache_key = to if to is not None else self._cast_to
+ cached = self._parsed_by_type.get(cache_key)
+ if cached is not None:
+ return cached # type: ignore[no-any-return]
+
+ if not self._is_sse_stream:
+ await self.read()
+
+ parsed = self._parse(to=to)
+ if is_given(self._options.post_parser):
+ parsed = self._options.post_parser(parsed)
+
+ self._parsed_by_type[cache_key] = parsed
+ return parsed
async def read(self) -> bytes:
- """Reads and returns the whole body."""
- return await self.http_response.aread()
+ """Read and return the binary response content."""
+ try:
+ return await self.http_response.aread()
+ except httpx.StreamConsumed as exc:
+ # the default error raised by httpx isn't very
+ # helpful in our case so we re-raise it with
+ # a different error message
+ raise StreamAlreadyConsumed() from exc
async def text(self) -> str:
- """The body decoded as text."""
+ """Read and decode the response content into a string."""
await self.read()
return self.http_response.text
- async def json(self) -> Any:
- """The body decoded as JSON."""
- return json.loads(await self.read())
+ async def json(self) -> object:
+ """Read and decode the JSON response content."""
+ await self.read()
+ return self.http_response.json()
+
+ async def close(self) -> None:
+ """Close the response and release the connection.
+
+ Automatically called if the response body is read to completion.
+ """
+ await self.http_response.aclose()
async def iter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]:
- """Iterates over the body in byte chunks, decoding any content encoding."""
+ """
+ A byte-iterator over the decoded response content.
+
+ This automatically handles gzip, deflate and brotli encoded responses.
+ """
async for chunk in self.http_response.aiter_bytes(chunk_size):
yield chunk
async def iter_text(self, chunk_size: int | None = None) -> AsyncIterator[str]:
- """Iterates over the body as decoded text chunks."""
+ """A str-iterator over the decoded response content
+ that handles both gzip, deflate, etc but also detects the content's
+ string encoding.
+ """
async for chunk in self.http_response.aiter_text(chunk_size):
yield chunk
async def iter_lines(self) -> AsyncIterator[str]:
- """Iterates over the body line by line."""
- async for line in self.http_response.aiter_lines():
- yield line
+ """Like `iter_text()` but will only yield chunks for each line"""
+ async for chunk in self.http_response.aiter_lines():
+ yield chunk
- async def close(self) -> None:
- """Releases the connection without reading the rest of the body."""
- await self.http_response.aclose()
+class BinaryAPIResponse(APIResponse[bytes]):
+ """Subclass of APIResponse providing helpers for dealing with binary data.
+
+ Note: If you want to stream the response data instead of eagerly reading it
+ all at once then you should use `.with_streaming_response` when making
+ the API request, e.g. `.with_streaming_response.get_binary_response()`
+ """
-class ResponseContextManager(Generic[_R]):
- """Sends the request on ``__enter__`` and closes the response on ``__exit__``."""
+ def write_to_file(
+ self,
+ file: str | os.PathLike[str],
+ ) -> None:
+ """Write the output to the given file.
+
+ Accepts a filename or any path-like object, e.g. pathlib.Path
+
+ Note: if you want to stream the data to the file instead of writing
+ all at once then you should use `.with_streaming_response` when making
+ the API request, e.g. `.with_streaming_response.get_binary_response()`
+ """
+ with open(file, mode="wb") as f:
+ for data in self.iter_bytes():
+ f.write(data)
+
+
+class AsyncBinaryAPIResponse(AsyncAPIResponse[bytes]):
+ """Subclass of APIResponse providing helpers for dealing with binary data.
+
+ Note: If you want to stream the response data instead of eagerly reading it
+ all at once then you should use `.with_streaming_response` when making
+ the API request, e.g. `.with_streaming_response.get_binary_response()`
+ """
+
+ async def write_to_file(
+ self,
+ file: str | os.PathLike[str],
+ ) -> None:
+ """Write the output to the given file.
- def __init__(self, send: Callable[[], APIResponse[_R]]) -> None:
- self._send = send
- self._response: APIResponse[_R] | None = None
+ Accepts a filename or any path-like object, e.g. pathlib.Path
- def __enter__(self) -> APIResponse[_R]:
- self._response = self._send()
- return self._response
+ Note: if you want to stream the data to the file instead of writing
+ all at once then you should use `.with_streaming_response` when making
+ the API request, e.g. `.with_streaming_response.get_binary_response()`
+ """
+ path = anyio.Path(file)
+ async with await path.open(mode="wb") as f:
+ async for data in self.iter_bytes():
+ await f.write(data)
+
+
+class StreamedBinaryAPIResponse(APIResponse[bytes]):
+ def stream_to_file(
+ self,
+ file: str | os.PathLike[str],
+ *,
+ chunk_size: int | None = None,
+ ) -> None:
+ """Streams the output to the given file.
+
+ Accepts a filename or any path-like object, e.g. pathlib.Path
+ """
+ with open(file, mode="wb") as f:
+ for data in self.iter_bytes(chunk_size):
+ f.write(data)
+
+
+class AsyncStreamedBinaryAPIResponse(AsyncAPIResponse[bytes]):
+ async def stream_to_file(
+ self,
+ file: str | os.PathLike[str],
+ *,
+ chunk_size: int | None = None,
+ ) -> None:
+ """Streams the output to the given file.
+
+ Accepts a filename or any path-like object, e.g. pathlib.Path
+ """
+ path = anyio.Path(file)
+ async with await path.open(mode="wb") as f:
+ async for data in self.iter_bytes(chunk_size):
+ await f.write(data)
+
+
+class MissingStreamClassError(TypeError):
+ def __init__(self) -> None:
+ super().__init__(
+ "The `stream` argument was set to `True` but the `stream_cls` argument was not given. See `opencode._streaming` for reference",
+ )
+
+
+class StreamAlreadyConsumed(OpencodeError):
+ """
+ Attempted to read or stream content, but the content has already
+ been streamed.
+
+ This can happen if you use a method like `.iter_lines()` and then attempt
+ to read th entire response body afterwards, e.g.
+
+ ```py
+ response = await client.post(...)
+ async for line in response.iter_lines():
+ ... # do something with `line`
+
+ content = await response.read()
+ # ^ error
+ ```
+
+ If you want this behaviour you'll need to either manually accumulate the response
+ content or call `await response.read()` before iterating over the stream.
+ """
+
+ def __init__(self) -> None:
+ message = (
+ "Attempted to read or stream some content, but the content has "
+ "already been streamed. "
+ "This could be due to attempting to stream the response "
+ "content more than once."
+ "\n\n"
+ "You can fix this by manually accumulating the response content while streaming "
+ "or by calling `.read()` before starting to stream."
+ )
+ super().__init__(message)
+
+
+class ResponseContextManager(Generic[_APIResponseT]):
+ """Context manager for ensuring that a request is not made
+ until it is entered and that the response will always be closed
+ when the context manager exits
+ """
+
+ def __init__(self, request_func: Callable[[], _APIResponseT]) -> None:
+ self._request_func = request_func
+ self.__response: _APIResponseT | None = None
+
+ def __enter__(self) -> _APIResponseT:
+ self.__response = self._request_func()
+ return self.__response
def __exit__(
- self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None
+ self,
+ exc_type: type[BaseException] | None,
+ exc: BaseException | None,
+ exc_tb: TracebackType | None,
) -> None:
- if self._response is not None:
- self._response.close()
+ if self.__response is not None:
+ self.__response.close()
-class AsyncResponseContextManager(Generic[_R]):
- """Sends the request on ``__aenter__`` and closes the response on ``__aexit__``."""
+class AsyncResponseContextManager(Generic[_AsyncAPIResponseT]):
+ """Context manager for ensuring that a request is not made
+ until it is entered and that the response will always be closed
+ when the context manager exits
+ """
- def __init__(self, send: Callable[[], Awaitable[AsyncAPIResponse[_R]]]) -> None:
- self._send = send
- self._response: AsyncAPIResponse[_R] | None = None
+ def __init__(self, api_request: Awaitable[_AsyncAPIResponseT]) -> None:
+ self._api_request = api_request
+ self.__response: _AsyncAPIResponseT | None = None
- async def __aenter__(self) -> AsyncAPIResponse[_R]:
- self._response = await self._send()
- return self._response
+ async def __aenter__(self) -> _AsyncAPIResponseT:
+ self.__response = await self._api_request
+ return self.__response
async def __aexit__(
- self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None
+ self,
+ exc_type: type[BaseException] | None,
+ exc: BaseException | None,
+ exc_tb: TracebackType | None,
) -> None:
- if self._response is not None:
- await self._response.close()
+ if self.__response is not None:
+ await self.__response.close()
-def _call_sync(mode: RawMode, func: Callable[..., Any], args: Any, kwargs: Any) -> Any:
- token = _raw_mode.set(mode)
- try:
- return func(*args, **kwargs)
- finally:
- _raw_mode.reset(token)
+def to_streamed_response_wrapper(func: Callable[P, R]) -> Callable[P, ResponseContextManager[APIResponse[R]]]:
+ """Higher order function that takes one of our bound API methods and wraps it
+ to support streaming and returning the raw `APIResponse` object directly.
+ """
+ @functools.wraps(func)
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[APIResponse[R]]:
+ extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+ extra_headers[RAW_RESPONSE_HEADER] = "stream"
+
+ kwargs["extra_headers"] = extra_headers
-async def _call_async(mode: RawMode, func: Callable[..., Any], args: Any, kwargs: Any) -> Any:
- token = _raw_mode.set(mode)
- try:
- # Async list methods are plain functions; in raw mode they return a coroutine for the first page.
- return await func(*args, **kwargs)
- finally:
- _raw_mode.reset(token)
+ make_request = functools.partial(func, *args, **kwargs)
+ return ResponseContextManager(cast(Callable[[], APIResponse[R]], make_request))
-def to_raw_response_wrapper(func: Callable[_P, _R]) -> Callable[_P, APIResponse[_R]]:
- """Wraps a sync generated method so it returns an :class:`APIResponse`."""
+ return wrapped
+
+
+def async_to_streamed_response_wrapper(
+ func: Callable[P, Awaitable[R]],
+) -> Callable[P, AsyncResponseContextManager[AsyncAPIResponse[R]]]:
+ """Higher order function that takes one of our bound API methods and wraps it
+ to support streaming and returning the raw `APIResponse` object directly.
+ """
@functools.wraps(func)
- def wrapped(*args: _P.args, **kwargs: _P.kwargs) -> APIResponse[_R]:
- return cast("APIResponse[_R]", _call_sync("raw", func, args, kwargs))
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[AsyncAPIResponse[R]]:
+ extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+ extra_headers[RAW_RESPONSE_HEADER] = "stream"
+
+ kwargs["extra_headers"] = extra_headers
+
+ make_request = func(*args, **kwargs)
+
+ return AsyncResponseContextManager(cast(Awaitable[AsyncAPIResponse[R]], make_request))
return wrapped
-def to_streamed_response_wrapper(func: Callable[_P, _R]) -> Callable[_P, ResponseContextManager[_R]]:
- """Wraps a sync generated method so it returns a context manager over an unread :class:`APIResponse`."""
+def to_custom_streamed_response_wrapper(
+ func: Callable[P, object],
+ response_cls: type[_APIResponseT],
+) -> Callable[P, ResponseContextManager[_APIResponseT]]:
+ """Higher order function that takes one of our bound API methods and an `APIResponse` class
+ and wraps the method to support streaming and returning the given response class directly.
+
+ Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
+ """
@functools.wraps(func)
- def wrapped(*args: _P.args, **kwargs: _P.kwargs) -> ResponseContextManager[_R]:
- return ResponseContextManager(lambda: cast("APIResponse[_R]", _call_sync("stream", func, args, kwargs)))
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> ResponseContextManager[_APIResponseT]:
+ extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+ extra_headers[RAW_RESPONSE_HEADER] = "stream"
+ extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls
+
+ kwargs["extra_headers"] = extra_headers
+
+ make_request = functools.partial(func, *args, **kwargs)
+
+ return ResponseContextManager(cast(Callable[[], _APIResponseT], make_request))
return wrapped
-@overload
-def async_to_raw_response_wrapper(
- func: Callable[_P, AsyncPaginator[_T]],
-) -> Callable[_P, Awaitable[AsyncAPIResponse[AsyncCursorPage[_T]]]]: ...
-@overload
-def async_to_raw_response_wrapper(
- func: Callable[_P, Awaitable[_R]],
-) -> Callable[_P, Awaitable[AsyncAPIResponse[_R]]]: ...
-def async_to_raw_response_wrapper(func: Callable[_P, Any]) -> Callable[_P, Awaitable[AsyncAPIResponse[Any]]]:
- """Wraps an async generated method so it returns an :class:`AsyncAPIResponse`."""
+def async_to_custom_streamed_response_wrapper(
+ func: Callable[P, Awaitable[object]],
+ response_cls: type[_AsyncAPIResponseT],
+) -> Callable[P, AsyncResponseContextManager[_AsyncAPIResponseT]]:
+ """Higher order function that takes one of our bound API methods and an `APIResponse` class
+ and wraps the method to support streaming and returning the given response class directly.
+
+ Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
+ """
@functools.wraps(func)
- async def wrapped(*args: _P.args, **kwargs: _P.kwargs) -> AsyncAPIResponse[Any]:
- return cast("AsyncAPIResponse[Any]", await _call_async("raw", func, args, kwargs))
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncResponseContextManager[_AsyncAPIResponseT]:
+ extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+ extra_headers[RAW_RESPONSE_HEADER] = "stream"
+ extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls
+
+ kwargs["extra_headers"] = extra_headers
+
+ make_request = func(*args, **kwargs)
+
+ return AsyncResponseContextManager(cast(Awaitable[_AsyncAPIResponseT], make_request))
return wrapped
-@overload
-def async_to_streamed_response_wrapper(
- func: Callable[_P, AsyncPaginator[_T]],
-) -> Callable[_P, AsyncResponseContextManager[AsyncCursorPage[_T]]]: ...
-@overload
-def async_to_streamed_response_wrapper(
- func: Callable[_P, Awaitable[_R]],
-) -> Callable[_P, AsyncResponseContextManager[_R]]: ...
-def async_to_streamed_response_wrapper(func: Callable[_P, Any]) -> Callable[_P, AsyncResponseContextManager[Any]]:
- """Wraps an async generated method so it returns an async context manager over an unread response."""
+def to_raw_response_wrapper(func: Callable[P, R]) -> Callable[P, APIResponse[R]]:
+ """Higher order function that takes one of our bound API methods and wraps it
+ to support returning the raw `APIResponse` object directly.
+ """
+
+ @functools.wraps(func)
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> APIResponse[R]:
+ extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+ extra_headers[RAW_RESPONSE_HEADER] = "raw"
+
+ kwargs["extra_headers"] = extra_headers
+
+ return cast(APIResponse[R], func(*args, **kwargs))
+
+ return wrapped
+
+
+def async_to_raw_response_wrapper(func: Callable[P, Awaitable[R]]) -> Callable[P, Awaitable[AsyncAPIResponse[R]]]:
+ """Higher order function that takes one of our bound API methods and wraps it
+ to support returning the raw `APIResponse` object directly.
+ """
+
+ @functools.wraps(func)
+ async def wrapped(*args: P.args, **kwargs: P.kwargs) -> AsyncAPIResponse[R]:
+ extra_headers: dict[str, str] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+ extra_headers[RAW_RESPONSE_HEADER] = "raw"
+
+ kwargs["extra_headers"] = extra_headers
+
+ return cast(AsyncAPIResponse[R], await func(*args, **kwargs))
+
+ return wrapped
+
+
+def to_custom_raw_response_wrapper(
+ func: Callable[P, object],
+ response_cls: type[_APIResponseT],
+) -> Callable[P, _APIResponseT]:
+ """Higher order function that takes one of our bound API methods and an `APIResponse` class
+ and wraps the method to support returning the given response class directly.
+
+ Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
+ """
@functools.wraps(func)
- def wrapped(*args: _P.args, **kwargs: _P.kwargs) -> AsyncResponseContextManager[Any]:
- return AsyncResponseContextManager(lambda: _call_async("stream", func, args, kwargs))
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> _APIResponseT:
+ extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+ extra_headers[RAW_RESPONSE_HEADER] = "raw"
+ extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls
+
+ kwargs["extra_headers"] = extra_headers
+
+ return cast(_APIResponseT, func(*args, **kwargs))
return wrapped
+
+
+def async_to_custom_raw_response_wrapper(
+ func: Callable[P, Awaitable[object]],
+ response_cls: type[_AsyncAPIResponseT],
+) -> Callable[P, Awaitable[_AsyncAPIResponseT]]:
+ """Higher order function that takes one of our bound API methods and an `APIResponse` class
+ and wraps the method to support returning the given response class directly.
+
+ Note: the given `response_cls` *must* be concrete, e.g. `class BinaryAPIResponse(APIResponse[bytes])`
+ """
+
+ @functools.wraps(func)
+ def wrapped(*args: P.args, **kwargs: P.kwargs) -> Awaitable[_AsyncAPIResponseT]:
+ extra_headers: dict[str, Any] = {**(cast(Any, kwargs.get("extra_headers")) or {})}
+ extra_headers[RAW_RESPONSE_HEADER] = "raw"
+ extra_headers[OVERRIDE_CAST_TO_HEADER] = response_cls
+
+ kwargs["extra_headers"] = extra_headers
+
+ return cast(Awaitable[_AsyncAPIResponseT], func(*args, **kwargs))
+
+ return wrapped
+
+
+def extract_response_type(typ: type[BaseAPIResponse[Any]]) -> type:
+ """Given a type like `APIResponse[T]`, returns the generic type variable `T`.
+
+ This also handles the case where a concrete subclass is given, e.g.
+ ```py
+ class MyResponse(APIResponse[bytes]):
+ ...
+
+ extract_response_type(MyResponse) -> bytes
+ ```
+ """
+ return extract_type_var_from_base(
+ typ,
+ generic_bases=cast("tuple[type, ...]", (BaseAPIResponse, APIResponse, AsyncAPIResponse)),
+ index=0,
+ )
diff --git a/src/opencode/_streaming.py b/src/opencode/_streaming.py
index 53ee463..6fa70be 100644
--- a/src/opencode/_streaming.py
+++ b/src/opencode/_streaming.py
@@ -1,248 +1,338 @@
-"""Server-sent event streams."""
-
+# Note: initially copied from https://github.com/florimondmanca/httpx-sse/blob/master/src/httpx_sse/_decoders.py
from __future__ import annotations
-import contextlib
import json
-import re
-import socket
-from dataclasses import dataclass, field
-from typing import TYPE_CHECKING, Any, Generic, TypeVar
+import inspect
+from types import TracebackType
+from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast
+from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable
import httpx
-import pydantic
-from typing_extensions import Self
-from ._exceptions import APIConnectionError, APIResponseValidationError, validation_summary
+from ._utils import extract_type_var_from_base
if TYPE_CHECKING:
- from collections.abc import AsyncGenerator, AsyncIterator, Callable, Generator, Iterator
- from types import TracebackType
+ from ._client import Opencode, AsyncOpencode
+ from ._models import FinalRequestOptions
-__all__ = ["AsyncStream", "ServerSentEvent", "Stream"]
_T = TypeVar("_T")
-MAX_EVENT_BYTES = 16 * 1024 * 1024
-"""Upper bound for one buffered event, matching the official TypeScript client."""
-
-
-@dataclass(frozen=True)
-class ServerSentEvent:
- """One decoded server-sent event."""
- data: str
- event: str | None = None
- id: str | None = None
- retry: int | None = None
+class Stream(Generic[_T]):
+ """Provides the core interface to iterate over a synchronous stream response."""
+ response: httpx.Response
+ _options: Optional[FinalRequestOptions] = None
+ _decoder: SSEBytesDecoder
-@dataclass
-class _Decoder:
- """Incremental SSE decoder following the WHATWG event-stream parsing rules."""
+ def __init__(
+ self,
+ *,
+ cast_to: type[_T],
+ response: httpx.Response,
+ client: Opencode,
+ options: Optional[FinalRequestOptions] = None,
+ ) -> None:
+ self.response = response
+ self._cast_to = cast_to
+ self._client = client
+ self._options = options
+ self._decoder = client._make_sse_decoder()
+ self._iterator = self.__stream__()
- _data: list[str] = field(default_factory=list[str])
- _event: str | None = None
- _id: str | None = None
- _retry: int | None = None
- _size: int = 0
+ def __next__(self) -> _T:
+ return self._iterator.__next__()
- def feed_line(self, line: str) -> ServerSentEvent | None:
- if line == "":
- return self._dispatch()
- if line.startswith(":"):
- return None
- name, _, value = line.partition(":")
- value = value.removeprefix(" ")
- if name == "data":
- self._size += len(value)
- if self._size > MAX_EVENT_BYTES:
- raise ValueError(f"Server-sent event exceeds {MAX_EVENT_BYTES} bytes")
- self._data.append(value)
- elif name == "event":
- self._event = value
- elif name == "id":
- if "\0" not in value:
- self._id = value
- elif name == "retry" and value.isdigit():
- self._retry = int(value)
- return None
+ def __iter__(self) -> Iterator[_T]:
+ for item in self._iterator:
+ yield item
- def _dispatch(self) -> ServerSentEvent | None:
- if not self._data:
- self._event = None
- return None
- event = ServerSentEvent(data="\n".join(self._data), event=self._event, id=self._id, retry=self._retry)
- self._data = []
- self._event = None
- self._retry = None
- self._size = 0
- return event
+ def _iter_events(self) -> Iterator[ServerSentEvent]:
+ yield from self._decoder.iter_bytes(self.response.iter_bytes())
+ def __stream__(self) -> Iterator[_T]:
+ cast_to = cast(Any, self._cast_to)
+ response = self.response
+ process_data = self._client._process_response_data
+ iterator = self._iter_events()
-_NEWLINE = re.compile(r"\r\n|\r|\n")
+ try:
+ for sse in iterator:
+ yield process_data(data=sse.json(), cast_to=cast_to, response=response)
+ finally:
+ # Ensure the response is closed even if the consumer doesn't read all data
+ response.close()
+ def __enter__(self) -> Self:
+ return self
-class _LineSplitter:
- """Splits decoded text into lines on CRLF, CR or LF, across chunk boundaries."""
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None,
+ exc: BaseException | None,
+ exc_tb: TracebackType | None,
+ ) -> None:
+ self.close()
- def __init__(self) -> None:
- self._buffer = ""
-
- def feed(self, chunk: str) -> list[str]:
- buffer = self._buffer + chunk
- lines: list[str] = []
- start = 0
- while (match := _NEWLINE.search(buffer, start)) is not None:
- if match.group() == "\r" and match.end() == len(buffer):
- break # may be the first half of a CRLF split across chunks
- lines.append(buffer[start : match.start()])
- start = match.end()
- self._buffer = buffer[start:]
- if len(self._buffer) > MAX_EVENT_BYTES:
- raise ValueError(f"Server-sent event line exceeds {MAX_EVENT_BYTES} bytes")
- return lines
-
- def flush(self) -> list[str]:
- """Returns the final partial line, then an empty line so a pending event is dispatched."""
- rest, self._buffer = self._buffer.removesuffix("\r"), ""
- return [rest, ""] if rest else [""]
-
-
-class _StreamBase(Generic[_T]):
- def __init__(self, response: httpx.Response, adapter: pydantic.TypeAdapter[_T]) -> None:
- self.response = response
- self._adapter = adapter
- self.on_activity: Callable[[], object] | None = None
- """Called for every chunk the stream receives, including keepalive comments that carry no event."""
+ def close(self) -> None:
+ """
+ Close the response and release the connection.
- def _activity(self) -> None:
- if self.on_activity is not None:
- self.on_activity()
+ Automatically called if the response body is read to completion.
+ """
+ self.response.close()
- def _decode(self, event: ServerSentEvent) -> _T:
- try:
- payload: Any = json.loads(event.data)
- except json.JSONDecodeError as exc:
- raise APIResponseValidationError(self.response, event.data, message="Malformed server-sent event") from exc
- try:
- return self._adapter.validate_python(payload)
- except pydantic.ValidationError as exc:
- raise APIResponseValidationError(self.response, payload, message=validation_summary(exc)) from exc
+class AsyncStream(Generic[_T]):
+ """Provides the core interface to iterate over an asynchronous stream response."""
-class Stream(_StreamBase[_T]):
- """A synchronous stream of decoded events.
+ response: httpx.Response
+ _options: Optional[FinalRequestOptions] = None
+ _decoder: SSEDecoder | SSEBytesDecoder
- Iterate it to receive events. Close it, or use it as a context manager, to release the connection::
+ def __init__(
+ self,
+ *,
+ cast_to: type[_T],
+ response: httpx.Response,
+ client: AsyncOpencode,
+ options: Optional[FinalRequestOptions] = None,
+ ) -> None:
+ self.response = response
+ self._cast_to = cast_to
+ self._client = client
+ self._options = options
+ self._decoder = client._make_sse_decoder()
+ self._iterator = self.__stream__()
- with client.event.subscribe() as events:
- for event in events:
- ...
- """
+ async def __anext__(self) -> _T:
+ return await self._iterator.__anext__()
- def __init__(self, response: httpx.Response, adapter: pydantic.TypeAdapter[_T]) -> None:
- super().__init__(response, adapter)
- self._iterator: Generator[_T, None, None] = self._events()
+ async def __aiter__(self) -> AsyncIterator[_T]:
+ async for item in self._iterator:
+ yield item
- def __iter__(self) -> Iterator[_T]:
- return self._iterator
+ async def _iter_events(self) -> AsyncIterator[ServerSentEvent]:
+ async for sse in self._decoder.aiter_bytes(self.response.aiter_bytes()):
+ yield sse
- def __next__(self) -> _T:
- return next(self._iterator)
+ async def __stream__(self) -> AsyncIterator[_T]:
+ cast_to = cast(Any, self._cast_to)
+ response = self.response
+ process_data = self._client._process_response_data
+ iterator = self._iter_events()
- def _events(self) -> Generator[_T, None, None]:
- decoder = _Decoder()
try:
- splitter = _LineSplitter()
- for chunk in self.response.iter_text():
- self._activity()
- for line in splitter.feed(chunk):
- if (event := decoder.feed_line(line)) is not None:
- yield self._decode(event)
- for line in splitter.flush():
- if (event := decoder.feed_line(line)) is not None:
- yield self._decode(event)
- except (httpx.TransportError, ValueError) as exc:
- if isinstance(exc, ValueError):
- raise APIResponseValidationError(self.response, None, message=str(exc)) from exc
- raise APIConnectionError(request=self.response.request, message=f"Stream interrupted: {exc}") from exc
+ async for sse in iterator:
+ yield process_data(data=sse.json(), cast_to=cast_to, response=response)
finally:
- self.response.close()
+ # Ensure the response is closed even if the consumer doesn't read all data
+ await response.aclose()
- def close(self) -> None:
- """Closes the underlying connection."""
- self._iterator.close()
- self.response.close()
+ async def __aenter__(self) -> Self:
+ return self
+
+ async def __aexit__(
+ self,
+ exc_type: type[BaseException] | None,
+ exc: BaseException | None,
+ exc_tb: TracebackType | None,
+ ) -> None:
+ await self.close()
- def interrupt(self) -> None:
- """Unblocks a read in progress on another thread, which then ends with :class:`APIConnectionError`.
+ async def close(self) -> None:
+ """
+ Close the response and release the connection.
- ``close`` must run on the iterating thread. Call this from any other thread first to wake it.
+ Automatically called if the response body is read to completion.
"""
- network_stream: Any = self.response.extensions.get("network_stream")
- sock: Any = network_stream.get_extra_info("socket") if network_stream is not None else None
- if isinstance(sock, socket.socket):
- with contextlib.suppress(OSError):
- sock.shutdown(socket.SHUT_RDWR)
+ await self.response.aclose()
- def __enter__(self) -> Self:
- return self
- def __exit__(
- self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None
+class ServerSentEvent:
+ def __init__(
+ self,
+ *,
+ event: str | None = None,
+ data: str | None = None,
+ id: str | None = None,
+ retry: int | None = None,
) -> None:
- self.close()
+ if data is None:
+ data = ""
+ self._id = id
+ self._data = data
+ self._event = event or None
+ self._retry = retry
-class AsyncStream(_StreamBase[_T]):
- """An asynchronous stream of decoded events.
+ @property
+ def event(self) -> str | None:
+ return self._event
- Iterate it with ``async for``. Close it, or use it as an async context manager, to release the connection::
+ @property
+ def id(self) -> str | None:
+ return self._id
- async with await client.event.subscribe() as events:
- async for event in events:
- ...
- """
+ @property
+ def retry(self) -> int | None:
+ return self._retry
- def __init__(self, response: httpx.Response, adapter: pydantic.TypeAdapter[_T]) -> None:
- super().__init__(response, adapter)
- self._iterator: AsyncGenerator[_T, None] = self._events()
+ @property
+ def data(self) -> str:
+ return self._data
- def __aiter__(self) -> AsyncIterator[_T]:
- return self._iterator
+ def json(self) -> Any:
+ return json.loads(self.data)
- async def __anext__(self) -> _T:
- return await self._iterator.__anext__()
+ @override
+ def __repr__(self) -> str:
+ return f"ServerSentEvent(event={self.event}, data={self.data}, id={self.id}, retry={self.retry})"
- async def _events(self) -> AsyncGenerator[_T, None]:
- decoder = _Decoder()
- try:
- splitter = _LineSplitter()
- async for chunk in self.response.aiter_text():
- self._activity()
- for line in splitter.feed(chunk):
- if (event := decoder.feed_line(line)) is not None:
- yield self._decode(event)
- for line in splitter.flush():
- if (event := decoder.feed_line(line)) is not None:
- yield self._decode(event)
- except (httpx.TransportError, ValueError) as exc:
- if isinstance(exc, ValueError):
- raise APIResponseValidationError(self.response, None, message=str(exc)) from exc
- raise APIConnectionError(request=self.response.request, message=f"Stream interrupted: {exc}") from exc
- finally:
- await self.response.aclose()
- async def close(self) -> None:
- """Closes the underlying connection."""
- await self._iterator.aclose()
- await self.response.aclose()
+class SSEDecoder:
+ _data: list[str]
+ _event: str | None
+ _retry: int | None
+ _last_event_id: str | None
- async def __aenter__(self) -> Self:
- return self
+ def __init__(self) -> None:
+ self._event = None
+ self._data = []
+ self._last_event_id = None
+ self._retry = None
- async def __aexit__(
- self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None
- ) -> None:
- await self.close()
+ def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]:
+ """Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
+ for chunk in self._iter_chunks(iterator):
+ # Split before decoding so splitlines() only uses \r and \n
+ for raw_line in chunk.splitlines():
+ line = raw_line.decode("utf-8")
+ sse = self.decode(line)
+ if sse:
+ yield sse
+
+ def _iter_chunks(self, iterator: Iterator[bytes]) -> Iterator[bytes]:
+ """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks"""
+ data = b""
+ for chunk in iterator:
+ for line in chunk.splitlines(keepends=True):
+ data += line
+ if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")):
+ yield data
+ data = b""
+ if data:
+ yield data
+
+ async def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]:
+ """Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
+ async for chunk in self._aiter_chunks(iterator):
+ # Split before decoding so splitlines() only uses \r and \n
+ for raw_line in chunk.splitlines():
+ line = raw_line.decode("utf-8")
+ sse = self.decode(line)
+ if sse:
+ yield sse
+
+ async def _aiter_chunks(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[bytes]:
+ """Given an iterator that yields raw binary data, iterate over it and yield individual SSE chunks"""
+ data = b""
+ async for chunk in iterator:
+ for line in chunk.splitlines(keepends=True):
+ data += line
+ if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")):
+ yield data
+ data = b""
+ if data:
+ yield data
+
+ def decode(self, line: str) -> ServerSentEvent | None:
+ # See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501
+
+ if not line:
+ if not self._event and not self._data and not self._last_event_id and self._retry is None:
+ return None
+
+ sse = ServerSentEvent(
+ event=self._event,
+ data="\n".join(self._data),
+ id=self._last_event_id,
+ retry=self._retry,
+ )
+
+ # NOTE: as per the SSE spec, do not reset last_event_id.
+ self._event = None
+ self._data = []
+ self._retry = None
+
+ return sse
+
+ if line.startswith(":"):
+ return None
+
+ fieldname, _, value = line.partition(":")
+
+ if value.startswith(" "):
+ value = value[1:]
+
+ if fieldname == "event":
+ self._event = value
+ elif fieldname == "data":
+ self._data.append(value)
+ elif fieldname == "id":
+ if "\0" in value:
+ pass
+ else:
+ self._last_event_id = value
+ elif fieldname == "retry":
+ try:
+ self._retry = int(value)
+ except (TypeError, ValueError):
+ pass
+ else:
+ pass # Field is ignored.
+
+ return None
+
+
+@runtime_checkable
+class SSEBytesDecoder(Protocol):
+ def iter_bytes(self, iterator: Iterator[bytes]) -> Iterator[ServerSentEvent]:
+ """Given an iterator that yields raw binary data, iterate over it & yield every event encountered"""
+ ...
+
+ def aiter_bytes(self, iterator: AsyncIterator[bytes]) -> AsyncIterator[ServerSentEvent]:
+ """Given an async iterator that yields raw binary data, iterate over it & yield every event encountered"""
+ ...
+
+
+def is_stream_class_type(typ: type) -> TypeGuard[type[Stream[object]] | type[AsyncStream[object]]]:
+ """TypeGuard for determining whether or not the given type is a subclass of `Stream` / `AsyncStream`"""
+ origin = get_origin(typ) or typ
+ return inspect.isclass(origin) and issubclass(origin, (Stream, AsyncStream))
+
+
+def extract_stream_chunk_type(
+ stream_cls: type,
+ *,
+ failure_message: str | None = None,
+) -> type:
+ """Given a type like `Stream[T]`, returns the generic type variable `T`.
+
+ This also handles the case where a concrete subclass is given, e.g.
+ ```py
+ class MyStream(Stream[bytes]):
+ ...
+
+ extract_stream_chunk_type(MyStream) -> bytes
+ ```
+ """
+ from ._base_client import Stream, AsyncStream
+
+ return extract_type_var_from_base(
+ stream_cls,
+ index=0,
+ generic_bases=cast("tuple[type, ...]", (Stream, AsyncStream)),
+ failure_message=failure_message,
+ )
diff --git a/src/opencode/_types.py b/src/opencode/_types.py
index 3c4e604..37ffc27 100644
--- a/src/opencode/_types.py
+++ b/src/opencode/_types.py
@@ -1,40 +1,151 @@
-"""Shared typing helpers used by the client and the generated code."""
-
from __future__ import annotations
-from collections.abc import Mapping
-from typing import Annotated, Any, Final, Literal, TypeAlias, TypeVar
+from os import PathLike
+from typing import (
+ IO,
+ TYPE_CHECKING,
+ Any,
+ Dict,
+ List,
+ Type,
+ Tuple,
+ Union,
+ Mapping,
+ TypeVar,
+ Callable,
+ Iterable,
+ Iterator,
+ Optional,
+ Sequence,
+ AsyncIterable,
+)
+from typing_extensions import (
+ Set,
+ Literal,
+ Protocol,
+ TypeAlias,
+ TypedDict,
+ SupportsIndex,
+ overload,
+ override,
+ runtime_checkable,
+)
import httpx
import pydantic
-from typing_extensions import override
-
-__all__ = [
- "NOT_GIVEN",
- "Body",
- "Headers",
- "NotGiven",
- "OpenEnum",
- "Query",
- "Timeout",
-]
+from httpx import URL, Proxy, Timeout, Response, BaseTransport, AsyncBaseTransport
+if TYPE_CHECKING:
+ from ._models import BaseModel
+ from ._response import APIResponse, AsyncAPIResponse
+
+Transport = BaseTransport
+AsyncTransport = AsyncBaseTransport
+Query = Mapping[str, object]
+Body = object
+AnyMapping = Mapping[str, object]
+ModelT = TypeVar("ModelT", bound=pydantic.BaseModel)
_T = TypeVar("_T")
+ArrayFormat = Literal["comma", "repeat", "indices", "brackets"]
+NestedFormat = Literal["dots", "brackets"]
+
+
+# Approximates httpx internal ProxiesTypes and RequestFiles types
+# while adding support for `PathLike` instances
+ProxiesDict = Dict["str | URL", Union[None, str, URL, Proxy]]
+ProxiesTypes = Union[str, Proxy, ProxiesDict]
+if TYPE_CHECKING:
+ Base64FileInput = Union[IO[bytes], PathLike[str]]
+ FileContent = Union[IO[bytes], bytes, PathLike[str]]
+else:
+ Base64FileInput = Union[IO[bytes], PathLike]
+ FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8.
+
+
+# Used for sending raw binary data / streaming data in request bodies
+# e.g. for file uploads without multipart encoding
+BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]]
+AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]]
+
+FileTypes = Union[
+ # file (or bytes)
+ FileContent,
+ # (filename, file (or bytes))
+ Tuple[Optional[str], FileContent],
+ # (filename, file (or bytes), content_type)
+ Tuple[Optional[str], FileContent, Optional[str]],
+ # (filename, file (or bytes), content_type, headers)
+ Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]],
+]
+RequestFiles = Union[Mapping[str, FileTypes], Sequence[Tuple[str, FileTypes]]]
+
+# duplicate of the above but without our custom file support
+HttpxFileContent = Union[IO[bytes], bytes]
+HttpxFileTypes = Union[
+ # file (or bytes)
+ HttpxFileContent,
+ # (filename, file (or bytes))
+ Tuple[Optional[str], HttpxFileContent],
+ # (filename, file (or bytes), content_type)
+ Tuple[Optional[str], HttpxFileContent, Optional[str]],
+ # (filename, file (or bytes), content_type, headers)
+ Tuple[Optional[str], HttpxFileContent, Optional[str], Mapping[str, str]],
+]
+HttpxRequestFiles = Union[Mapping[str, HttpxFileTypes], Sequence[Tuple[str, HttpxFileTypes]]]
+
+# Workaround to support (cast_to: Type[ResponseT]) -> ResponseT
+# where ResponseT includes `None`. In order to support directly
+# passing `None`, overloads would have to be defined for every
+# method that uses `ResponseT` which would lead to an unacceptable
+# amount of code duplication and make it unreadable. See _base_client.py
+# for example usage.
+#
+# This unfortunately means that you will either have
+# to import this type and pass it explicitly:
+#
+# from opencode import NoneType
+# client.get('/foo', cast_to=NoneType)
+#
+# or build it yourself:
+#
+# client.get('/foo', cast_to=type(None))
+if TYPE_CHECKING:
+ NoneType: Type[None]
+else:
+ NoneType = type(None)
-class NotGiven:
- """Marks an argument that was not passed, as opposed to one passed as ``None``.
- The OpenCode API distinguishes a field that is absent from a field set to ``null``, so optional
- arguments default to ``NOT_GIVEN`` and are left out of the request entirely.
+class RequestOptions(TypedDict, total=False):
+ headers: Headers
+ max_retries: int
+ timeout: float | Timeout | None
+ params: Query
+ extra_json: AnyMapping
+ idempotency_key: str
+ follow_redirects: bool
+
+
+# Sentinel class used until PEP 0661 is accepted
+class NotGiven:
"""
+ For parameters with a meaningful None value, we need to distinguish between
+ the user explicitly passing None, and the user not passing the parameter at
+ all.
+
+ User code shouldn't need to use not_given directly.
+
+ For example:
+
+ ```py
+ def create(timeout: Timeout | None | NotGiven = not_given): ...
- _instance: NotGiven | None = None
- def __new__(cls) -> NotGiven:
- if cls._instance is None:
- cls._instance = super().__new__(cls)
- return cls._instance
+ create(timeout=1) # 1s timeout
+ create(timeout=None) # No timeout
+ create() # Default timeout behavior
+ ```
+ """
def __bool__(self) -> Literal[False]:
return False
@@ -43,35 +154,120 @@ def __bool__(self) -> Literal[False]:
def __repr__(self) -> str:
return "NOT_GIVEN"
- @override
- def __reduce__(self) -> str:
- return "NOT_GIVEN"
+not_given = NotGiven()
+# for backwards compatibility:
+NOT_GIVEN = NotGiven()
+
+
+class Omit:
+ """
+ To explicitly omit something from being sent in a request, use `omit`.
+
+ ```py
+ # as the default `Content-Type` header is `application/json` that will be sent
+ client.post("/upload/files", files={"file": b"my raw file content"})
+
+ # you can't explicitly override the header as it has to be dynamically generated
+ # to look something like: 'multipart/form-data; boundary=0d8382fcf5f8c3be01ca2e11002d2983'
+ client.post(..., headers={"Content-Type": "multipart/form-data"})
+
+ # instead you can remove the default `application/json` header by passing omit
+ client.post(..., headers={"Content-Type": omit})
+ ```
+ """
+
+ def __bool__(self) -> Literal[False]:
+ return False
+
+
+omit = Omit()
+
+
+@runtime_checkable
+class ModelBuilderProtocol(Protocol):
+ @classmethod
+ def build(
+ cls: type[_T],
+ *,
+ response: Response,
+ data: object,
+ ) -> _T: ...
+
+
+Headers = Mapping[str, Union[str, Omit]]
+
+
+class HeadersLikeProtocol(Protocol):
+ def get(self, __key: str) -> str | None: ...
+
+
+HeadersLike = Union[Headers, HeadersLikeProtocol]
+
+ResponseT = TypeVar(
+ "ResponseT",
+ bound=Union[
+ object,
+ str,
+ None,
+ "BaseModel",
+ List[Any],
+ Dict[str, Any],
+ Response,
+ ModelBuilderProtocol,
+ "APIResponse[Any]",
+ "AsyncAPIResponse[Any]",
+ ],
+)
+
+StrBytesIntFloat = Union[str, bytes, int, float]
+
+# Note: copied from Pydantic
+# https://github.com/pydantic/pydantic/blob/6f31f8f68ef011f84357330186f603ff295312fd/pydantic/main.py#L79
+IncEx: TypeAlias = Union[Set[int], Set[str], Mapping[int, Union["IncEx", bool]], Mapping[str, Union["IncEx", bool]]]
+
+PostParser = Callable[[Any], Any]
+
+
+@runtime_checkable
+class InheritsGeneric(Protocol):
+ """Represents a type that has inherited from `Generic`
+
+ The `__orig_bases__` property can be used to determine the resolved
+ type variable for a given base class.
+ """
-NOT_GIVEN: Final = NotGiven()
+ __orig_bases__: tuple[_GenericAlias]
-Headers: TypeAlias = Mapping[str, str | NotGiven]
-"""Extra request headers. A ``NOT_GIVEN`` value removes a header the client would otherwise send."""
-Query: TypeAlias = Mapping[str, object]
-"""Extra query parameters, encoded the same way as generated parameters."""
+class _GenericAlias(Protocol):
+ __origin__: type[object]
-Body: TypeAlias = Mapping[str, object]
-"""Extra JSON body fields, merged over the generated body."""
-Timeout: TypeAlias = float | httpx.Timeout | None
-"""A timeout in seconds, an ``httpx.Timeout``, or ``None`` to wait forever."""
+class HttpxSendArgs(TypedDict, total=False):
+ auth: httpx.Auth
+ follow_redirects: bool
-def _accept_unknown_value(value: Any, handler: pydantic.ValidatorFunctionWrapHandler) -> Any:
- try:
- return handler(value)
- except pydantic.ValidationError:
- # Newer servers may add enum members. Keep the raw value instead of failing the whole response.
- if isinstance(value, (str, int, float, bool)):
- return value
- raise
+_T_co = TypeVar("_T_co", covariant=True)
-OpenEnum = Annotated[_T, pydantic.WrapValidator(_accept_unknown_value)]
-"""A ``Literal`` enum that type-checks strictly but accepts values added by newer servers at runtime."""
+if TYPE_CHECKING:
+ # This works because str.__contains__ does not accept object (either in typeshed or at runtime)
+ # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285
+ #
+ # Note: index() and count() methods are intentionally omitted to allow pyright to properly
+ # infer TypedDict types when dict literals are used in lists assigned to SequenceNotStr.
+ class SequenceNotStr(Protocol[_T_co]):
+ @overload
+ def __getitem__(self, index: SupportsIndex, /) -> _T_co: ...
+ @overload
+ def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ...
+ def __contains__(self, value: object, /) -> bool: ...
+ def __len__(self) -> int: ...
+ def __iter__(self) -> Iterator[_T_co]: ...
+ def __reversed__(self) -> Iterator[_T_co]: ...
+else:
+ # just point this to a normal `Sequence` at runtime to avoid having to special case
+ # deserializing our custom sequence type
+ SequenceNotStr = Sequence
diff --git a/src/opencode/_utils/__init__.py b/src/opencode/_utils/__init__.py
new file mode 100644
index 0000000..1c090e5
--- /dev/null
+++ b/src/opencode/_utils/__init__.py
@@ -0,0 +1,64 @@
+from ._path import path_template as path_template
+from ._sync import asyncify as asyncify
+from ._proxy import LazyProxy as LazyProxy
+from ._utils import (
+ flatten as flatten,
+ is_dict as is_dict,
+ is_list as is_list,
+ is_given as is_given,
+ is_tuple as is_tuple,
+ json_safe as json_safe,
+ lru_cache as lru_cache,
+ is_mapping as is_mapping,
+ is_tuple_t as is_tuple_t,
+ is_iterable as is_iterable,
+ is_sequence as is_sequence,
+ coerce_float as coerce_float,
+ is_mapping_t as is_mapping_t,
+ removeprefix as removeprefix,
+ removesuffix as removesuffix,
+ extract_files as extract_files,
+ is_sequence_t as is_sequence_t,
+ required_args as required_args,
+ coerce_boolean as coerce_boolean,
+ coerce_integer as coerce_integer,
+ file_from_path as file_from_path,
+ strip_not_given as strip_not_given,
+ get_async_library as get_async_library,
+ maybe_coerce_float as maybe_coerce_float,
+ get_required_header as get_required_header,
+ maybe_coerce_boolean as maybe_coerce_boolean,
+ maybe_coerce_integer as maybe_coerce_integer,
+)
+from ._compat import (
+ get_args as get_args,
+ is_union as is_union,
+ get_origin as get_origin,
+ is_typeddict as is_typeddict,
+ is_literal_type as is_literal_type,
+)
+from ._typing import (
+ is_list_type as is_list_type,
+ is_union_type as is_union_type,
+ extract_type_arg as extract_type_arg,
+ is_iterable_type as is_iterable_type,
+ is_required_type as is_required_type,
+ is_sequence_type as is_sequence_type,
+ is_annotated_type as is_annotated_type,
+ is_type_alias_type as is_type_alias_type,
+ strip_annotated_type as strip_annotated_type,
+ extract_type_var_from_base as extract_type_var_from_base,
+)
+from ._streams import consume_sync_iterator as consume_sync_iterator, consume_async_iterator as consume_async_iterator
+from ._transform import (
+ PropertyInfo as PropertyInfo,
+ transform as transform,
+ async_transform as async_transform,
+ maybe_transform as maybe_transform,
+ async_maybe_transform as async_maybe_transform,
+)
+from ._reflection import (
+ function_has_argument as function_has_argument,
+ assert_signatures_in_sync as assert_signatures_in_sync,
+)
+from ._datetime_parse import parse_date as parse_date, parse_datetime as parse_datetime
diff --git a/src/opencode/_utils/_compat.py b/src/opencode/_utils/_compat.py
new file mode 100644
index 0000000..2c70b29
--- /dev/null
+++ b/src/opencode/_utils/_compat.py
@@ -0,0 +1,45 @@
+from __future__ import annotations
+
+import sys
+import typing_extensions
+from typing import Any, Type, Union, Literal, Optional
+from datetime import date, datetime
+from typing_extensions import get_args as _get_args, get_origin as _get_origin
+
+from .._types import StrBytesIntFloat
+from ._datetime_parse import parse_date as _parse_date, parse_datetime as _parse_datetime
+
+_LITERAL_TYPES = {Literal, typing_extensions.Literal}
+
+
+def get_args(tp: type[Any]) -> tuple[Any, ...]:
+ return _get_args(tp)
+
+
+def get_origin(tp: type[Any]) -> type[Any] | None:
+ return _get_origin(tp)
+
+
+def is_union(tp: Optional[Type[Any]]) -> bool:
+ if sys.version_info < (3, 10):
+ return tp is Union # type: ignore[comparison-overlap]
+ else:
+ import types
+
+ return tp is Union or tp is types.UnionType # type: ignore[comparison-overlap]
+
+
+def is_typeddict(tp: Type[Any]) -> bool:
+ return typing_extensions.is_typeddict(tp)
+
+
+def is_literal_type(tp: Type[Any]) -> bool:
+ return get_origin(tp) in _LITERAL_TYPES
+
+
+def parse_date(value: Union[date, StrBytesIntFloat]) -> date:
+ return _parse_date(value)
+
+
+def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime:
+ return _parse_datetime(value)
diff --git a/src/opencode/_utils/_datetime_parse.py b/src/opencode/_utils/_datetime_parse.py
new file mode 100644
index 0000000..7cb9d9e
--- /dev/null
+++ b/src/opencode/_utils/_datetime_parse.py
@@ -0,0 +1,136 @@
+"""
+This file contains code from https://github.com/pydantic/pydantic/blob/main/pydantic/v1/datetime_parse.py
+without the Pydantic v1 specific errors.
+"""
+
+from __future__ import annotations
+
+import re
+from typing import Dict, Union, Optional
+from datetime import date, datetime, timezone, timedelta
+
+from .._types import StrBytesIntFloat
+
+date_expr = r"(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})"
+time_expr = (
+ r"(?P\d{1,2}):(?P\d{1,2})"
+ r"(?::(?P\d{1,2})(?:\.(?P\d{1,6})\d{0,6})?)?"
+ r"(?PZ|[+-]\d{2}(?::?\d{2})?)?$"
+)
+
+date_re = re.compile(f"{date_expr}$")
+datetime_re = re.compile(f"{date_expr}[T ]{time_expr}")
+
+
+EPOCH = datetime(1970, 1, 1)
+# if greater than this, the number is in ms, if less than or equal it's in seconds
+# (in seconds this is 11th October 2603, in ms it's 20th August 1970)
+MS_WATERSHED = int(2e10)
+# slightly more than datetime.max in ns - (datetime.max - EPOCH).total_seconds() * 1e9
+MAX_NUMBER = int(3e20)
+
+
+def _get_numeric(value: StrBytesIntFloat, native_expected_type: str) -> Union[None, int, float]:
+ if isinstance(value, (int, float)):
+ return value
+ try:
+ return float(value)
+ except ValueError:
+ return None
+ except TypeError:
+ raise TypeError(f"invalid type; expected {native_expected_type}, string, bytes, int or float") from None
+
+
+def _from_unix_seconds(seconds: Union[int, float]) -> datetime:
+ if seconds > MAX_NUMBER:
+ return datetime.max
+ elif seconds < -MAX_NUMBER:
+ return datetime.min
+
+ while abs(seconds) > MS_WATERSHED:
+ seconds /= 1000
+ dt = EPOCH + timedelta(seconds=seconds)
+ return dt.replace(tzinfo=timezone.utc)
+
+
+def _parse_timezone(value: Optional[str]) -> Union[None, int, timezone]:
+ if value == "Z":
+ return timezone.utc
+ elif value is not None:
+ offset_mins = int(value[-2:]) if len(value) > 3 else 0
+ offset = 60 * int(value[1:3]) + offset_mins
+ if value[0] == "-":
+ offset = -offset
+ return timezone(timedelta(minutes=offset))
+ else:
+ return None
+
+
+def parse_datetime(value: Union[datetime, StrBytesIntFloat]) -> datetime:
+ """
+ Parse a datetime/int/float/string and return a datetime.datetime.
+
+ This function supports time zone offsets. When the input contains one,
+ the output uses a timezone with a fixed offset from UTC.
+
+ Raise ValueError if the input is well formatted but not a valid datetime.
+ Raise ValueError if the input isn't well formatted.
+ """
+ if isinstance(value, datetime):
+ return value
+
+ number = _get_numeric(value, "datetime")
+ if number is not None:
+ return _from_unix_seconds(number)
+
+ if isinstance(value, bytes):
+ value = value.decode()
+
+ assert not isinstance(value, (float, int))
+
+ match = datetime_re.match(value)
+ if match is None:
+ raise ValueError("invalid datetime format")
+
+ kw = match.groupdict()
+ if kw["microsecond"]:
+ kw["microsecond"] = kw["microsecond"].ljust(6, "0")
+
+ tzinfo = _parse_timezone(kw.pop("tzinfo"))
+ kw_: Dict[str, Union[None, int, timezone]] = {k: int(v) for k, v in kw.items() if v is not None}
+ kw_["tzinfo"] = tzinfo
+
+ return datetime(**kw_) # type: ignore
+
+
+def parse_date(value: Union[date, StrBytesIntFloat]) -> date:
+ """
+ Parse a date/int/float/string and return a datetime.date.
+
+ Raise ValueError if the input is well formatted but not a valid date.
+ Raise ValueError if the input isn't well formatted.
+ """
+ if isinstance(value, date):
+ if isinstance(value, datetime):
+ return value.date()
+ else:
+ return value
+
+ number = _get_numeric(value, "date")
+ if number is not None:
+ return _from_unix_seconds(number).date()
+
+ if isinstance(value, bytes):
+ value = value.decode()
+
+ assert not isinstance(value, (float, int))
+ match = date_re.match(value)
+ if match is None:
+ raise ValueError("invalid date format")
+
+ kw = {k: int(v) for k, v in match.groupdict().items()}
+
+ try:
+ return date(**kw)
+ except ValueError:
+ raise ValueError("invalid date format") from None
diff --git a/src/opencode/_utils/_json.py b/src/opencode/_utils/_json.py
new file mode 100644
index 0000000..6058421
--- /dev/null
+++ b/src/opencode/_utils/_json.py
@@ -0,0 +1,35 @@
+import json
+from typing import Any
+from datetime import datetime
+from typing_extensions import override
+
+import pydantic
+
+from .._compat import model_dump
+
+
+def openapi_dumps(obj: Any) -> bytes:
+ """
+ Serialize an object to UTF-8 encoded JSON bytes.
+
+ Extends the standard json.dumps with support for additional types
+ commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc.
+ """
+ return json.dumps(
+ obj,
+ cls=_CustomEncoder,
+ # Uses the same defaults as httpx's JSON serialization
+ ensure_ascii=False,
+ separators=(",", ":"),
+ allow_nan=False,
+ ).encode()
+
+
+class _CustomEncoder(json.JSONEncoder):
+ @override
+ def default(self, o: Any) -> Any:
+ if isinstance(o, datetime):
+ return o.isoformat()
+ if isinstance(o, pydantic.BaseModel):
+ return model_dump(o, exclude_unset=True, mode="json", by_alias=True)
+ return super().default(o)
diff --git a/src/opencode/_utils/_logs.py b/src/opencode/_utils/_logs.py
new file mode 100644
index 0000000..e1b3dca
--- /dev/null
+++ b/src/opencode/_utils/_logs.py
@@ -0,0 +1,25 @@
+import os
+import logging
+
+logger: logging.Logger = logging.getLogger("opencode")
+httpx_logger: logging.Logger = logging.getLogger("httpx")
+
+
+def _basic_config() -> None:
+ # e.g. [2023-10-05 14:12:26 - opencode._base_client:818 - DEBUG] HTTP Request: POST http://127.0.0.1:4010/foo/bar "200 OK"
+ logging.basicConfig(
+ format="[%(asctime)s - %(name)s:%(lineno)d - %(levelname)s] %(message)s",
+ datefmt="%Y-%m-%d %H:%M:%S",
+ )
+
+
+def setup_logging() -> None:
+ env = os.environ.get("OPENCODE_LOG")
+ if env == "debug":
+ _basic_config()
+ logger.setLevel(logging.DEBUG)
+ httpx_logger.setLevel(logging.DEBUG)
+ elif env == "info":
+ _basic_config()
+ logger.setLevel(logging.INFO)
+ httpx_logger.setLevel(logging.INFO)
diff --git a/src/opencode/_utils/_path.py b/src/opencode/_utils/_path.py
new file mode 100644
index 0000000..4d6e1e4
--- /dev/null
+++ b/src/opencode/_utils/_path.py
@@ -0,0 +1,127 @@
+from __future__ import annotations
+
+import re
+from typing import (
+ Any,
+ Mapping,
+ Callable,
+)
+from urllib.parse import quote
+
+# Matches '.' or '..' where each dot is either literal or percent-encoded (%2e / %2E).
+_DOT_SEGMENT_RE = re.compile(r"^(?:\.|%2[eE]){1,2}$")
+
+_PLACEHOLDER_RE = re.compile(r"\{(\w+)\}")
+
+
+def _quote_path_segment_part(value: str) -> str:
+ """Percent-encode `value` for use in a URI path segment.
+
+ Considers characters not in `pchar` set from RFC 3986 §3.3 to be unsafe.
+ https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
+ """
+ # quote() already treats unreserved characters (letters, digits, and -._~)
+ # as safe, so we only need to add sub-delims, ':', and '@'.
+ # Notably, unlike the default `safe` for quote(), / is unsafe and must be quoted.
+ return quote(value, safe="!$&'()*+,;=:@")
+
+
+def _quote_query_part(value: str) -> str:
+ """Percent-encode `value` for use in a URI query string.
+
+ Considers &, = and characters not in `query` set from RFC 3986 §3.4 to be unsafe.
+ https://datatracker.ietf.org/doc/html/rfc3986#section-3.4
+ """
+ return quote(value, safe="!$'()*+,;:@/?")
+
+
+def _quote_fragment_part(value: str) -> str:
+ """Percent-encode `value` for use in a URI fragment.
+
+ Considers characters not in `fragment` set from RFC 3986 §3.5 to be unsafe.
+ https://datatracker.ietf.org/doc/html/rfc3986#section-3.5
+ """
+ return quote(value, safe="!$&'()*+,;=:@/?")
+
+
+def _interpolate(
+ template: str,
+ values: Mapping[str, Any],
+ quoter: Callable[[str], str],
+) -> str:
+ """Replace {name} placeholders in `template`, quoting each value with `quoter`.
+
+ Placeholder names are looked up in `values`.
+
+ Raises:
+ KeyError: If a placeholder is not found in `values`.
+ """
+ # re.split with a capturing group returns alternating
+ # [text, name, text, name, ..., text] elements.
+ parts = _PLACEHOLDER_RE.split(template)
+
+ for i in range(1, len(parts), 2):
+ name = parts[i]
+ if name not in values:
+ raise KeyError(f"a value for placeholder {{{name}}} was not provided")
+ val = values[name]
+ if val is None:
+ parts[i] = "null"
+ elif isinstance(val, bool):
+ parts[i] = "true" if val else "false"
+ else:
+ parts[i] = quoter(str(values[name]))
+
+ return "".join(parts)
+
+
+def path_template(template: str, /, **kwargs: Any) -> str:
+ """Interpolate {name} placeholders in `template` from keyword arguments.
+
+ Args:
+ template: The template string containing {name} placeholders.
+ **kwargs: Keyword arguments to interpolate into the template.
+
+ Returns:
+ The template with placeholders interpolated and percent-encoded.
+
+ Safe characters for percent-encoding are dependent on the URI component.
+ Placeholders in path and fragment portions are percent-encoded where the `segment`
+ and `fragment` sets from RFC 3986 respectively are considered safe.
+ Placeholders in the query portion are percent-encoded where the `query` set from
+ RFC 3986 §3.3 is considered safe except for = and & characters.
+
+ Raises:
+ KeyError: If a placeholder is not found in `kwargs`.
+ ValueError: If resulting path contains /./ or /../ segments (including percent-encoded dot-segments).
+ """
+ # Split the template into path, query, and fragment portions.
+ fragment_template: str | None = None
+ query_template: str | None = None
+
+ rest = template
+ if "#" in rest:
+ rest, fragment_template = rest.split("#", 1)
+ if "?" in rest:
+ rest, query_template = rest.split("?", 1)
+ path_template = rest
+
+ # Interpolate each portion with the appropriate quoting rules.
+ path_result = _interpolate(path_template, kwargs, _quote_path_segment_part)
+
+ # Reject dot-segments (. and ..) in the final assembled path. The check
+ # runs after interpolation so that adjacent placeholders or a mix of static
+ # text and placeholders that together form a dot-segment are caught.
+ # Also reject percent-encoded dot-segments to protect against incorrectly
+ # implemented normalization in servers/proxies.
+ for segment in path_result.split("/"):
+ if _DOT_SEGMENT_RE.match(segment):
+ raise ValueError(f"Constructed path {path_result!r} contains dot-segment {segment!r} which is not allowed")
+
+ result = path_result
+ if query_template is not None:
+ result += "?" + _interpolate(query_template, kwargs, _quote_query_part)
+ if fragment_template is not None:
+ result += "#" + _interpolate(fragment_template, kwargs, _quote_fragment_part)
+
+ return result
diff --git a/src/opencode/_utils/_proxy.py b/src/opencode/_utils/_proxy.py
new file mode 100644
index 0000000..0f239a3
--- /dev/null
+++ b/src/opencode/_utils/_proxy.py
@@ -0,0 +1,65 @@
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+from typing import Generic, TypeVar, Iterable, cast
+from typing_extensions import override
+
+T = TypeVar("T")
+
+
+class LazyProxy(Generic[T], ABC):
+ """Implements data methods to pretend that an instance is another instance.
+
+ This includes forwarding attribute access and other methods.
+ """
+
+ # Note: we have to special case proxies that themselves return proxies
+ # to support using a proxy as a catch-all for any random access, e.g. `proxy.foo.bar.baz`
+
+ def __getattr__(self, attr: str) -> object:
+ proxied = self.__get_proxied__()
+ if isinstance(proxied, LazyProxy):
+ return proxied # pyright: ignore
+ return getattr(proxied, attr)
+
+ @override
+ def __repr__(self) -> str:
+ proxied = self.__get_proxied__()
+ if isinstance(proxied, LazyProxy):
+ return proxied.__class__.__name__
+ return repr(self.__get_proxied__())
+
+ @override
+ def __str__(self) -> str:
+ proxied = self.__get_proxied__()
+ if isinstance(proxied, LazyProxy):
+ return proxied.__class__.__name__
+ return str(proxied)
+
+ @override
+ def __dir__(self) -> Iterable[str]:
+ proxied = self.__get_proxied__()
+ if isinstance(proxied, LazyProxy):
+ return []
+ return proxied.__dir__()
+
+ @property # type: ignore
+ @override
+ def __class__(self) -> type: # pyright: ignore
+ try:
+ proxied = self.__get_proxied__()
+ except Exception:
+ return type(self)
+ if issubclass(type(proxied), LazyProxy):
+ return type(proxied)
+ return proxied.__class__
+
+ def __get_proxied__(self) -> T:
+ return self.__load__()
+
+ def __as_proxied__(self) -> T:
+ """Helper method that returns the current proxy, typed as the loaded object"""
+ return cast(T, self)
+
+ @abstractmethod
+ def __load__(self) -> T: ...
diff --git a/src/opencode/_utils/_reflection.py b/src/opencode/_utils/_reflection.py
new file mode 100644
index 0000000..89aa712
--- /dev/null
+++ b/src/opencode/_utils/_reflection.py
@@ -0,0 +1,42 @@
+from __future__ import annotations
+
+import inspect
+from typing import Any, Callable
+
+
+def function_has_argument(func: Callable[..., Any], arg_name: str) -> bool:
+ """Returns whether or not the given function has a specific parameter"""
+ sig = inspect.signature(func)
+ return arg_name in sig.parameters
+
+
+def assert_signatures_in_sync(
+ source_func: Callable[..., Any],
+ check_func: Callable[..., Any],
+ *,
+ exclude_params: set[str] = set(),
+) -> None:
+ """Ensure that the signature of the second function matches the first."""
+
+ check_sig = inspect.signature(check_func)
+ source_sig = inspect.signature(source_func)
+
+ errors: list[str] = []
+
+ for name, source_param in source_sig.parameters.items():
+ if name in exclude_params:
+ continue
+
+ custom_param = check_sig.parameters.get(name)
+ if not custom_param:
+ errors.append(f"the `{name}` param is missing")
+ continue
+
+ if custom_param.annotation != source_param.annotation:
+ errors.append(
+ f"types for the `{name}` param are do not match; source={repr(source_param.annotation)} checking={repr(custom_param.annotation)}"
+ )
+ continue
+
+ if errors:
+ raise AssertionError(f"{len(errors)} errors encountered when comparing signatures:\n\n" + "\n\n".join(errors))
diff --git a/src/opencode/_utils/_resources_proxy.py b/src/opencode/_utils/_resources_proxy.py
new file mode 100644
index 0000000..44dc13c
--- /dev/null
+++ b/src/opencode/_utils/_resources_proxy.py
@@ -0,0 +1,24 @@
+from __future__ import annotations
+
+from typing import Any
+from typing_extensions import override
+
+from ._proxy import LazyProxy
+
+
+class ResourcesProxy(LazyProxy[Any]):
+ """A proxy for the `opencode.resources` module.
+
+ This is used so that we can lazily import `opencode.resources` only when
+ needed *and* so that users can just import `opencode` and reference `opencode.resources`
+ """
+
+ @override
+ def __load__(self) -> Any:
+ import importlib
+
+ mod = importlib.import_module("opencode.resources")
+ return mod
+
+
+resources = ResourcesProxy().__as_proxied__()
diff --git a/src/opencode/_utils/_streams.py b/src/opencode/_utils/_streams.py
new file mode 100644
index 0000000..f4a0208
--- /dev/null
+++ b/src/opencode/_utils/_streams.py
@@ -0,0 +1,12 @@
+from typing import Any
+from typing_extensions import Iterator, AsyncIterator
+
+
+def consume_sync_iterator(iterator: Iterator[Any]) -> None:
+ for _ in iterator:
+ ...
+
+
+async def consume_async_iterator(iterator: AsyncIterator[Any]) -> None:
+ async for _ in iterator:
+ ...
diff --git a/src/opencode/_utils/_sync.py b/src/opencode/_utils/_sync.py
new file mode 100644
index 0000000..f6027c1
--- /dev/null
+++ b/src/opencode/_utils/_sync.py
@@ -0,0 +1,58 @@
+from __future__ import annotations
+
+import asyncio
+import functools
+from typing import TypeVar, Callable, Awaitable
+from typing_extensions import ParamSpec
+
+import anyio
+import sniffio
+import anyio.to_thread
+
+T_Retval = TypeVar("T_Retval")
+T_ParamSpec = ParamSpec("T_ParamSpec")
+
+
+async def to_thread(
+ func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs
+) -> T_Retval:
+ if sniffio.current_async_library() == "asyncio":
+ return await asyncio.to_thread(func, *args, **kwargs)
+
+ return await anyio.to_thread.run_sync(
+ functools.partial(func, *args, **kwargs),
+ )
+
+
+# inspired by `asyncer`, https://github.com/tiangolo/asyncer
+def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]:
+ """
+ Take a blocking function and create an async one that receives the same
+ positional and keyword arguments.
+
+ Usage:
+
+ ```python
+ def blocking_func(arg1, arg2, kwarg1=None):
+ # blocking code
+ return result
+
+
+ result = asyncify(blocking_function)(arg1, arg2, kwarg1=value1)
+ ```
+
+ ## Arguments
+
+ `function`: a blocking regular callable (e.g. a function)
+
+ ## Return
+
+ An async function that takes the same positional and keyword arguments as the
+ original one, that when called runs the same original function in a thread worker
+ and returns the result.
+ """
+
+ async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval:
+ return await to_thread(function, *args, **kwargs)
+
+ return wrapper
diff --git a/src/opencode/_utils/_transform.py b/src/opencode/_utils/_transform.py
new file mode 100644
index 0000000..5207549
--- /dev/null
+++ b/src/opencode/_utils/_transform.py
@@ -0,0 +1,457 @@
+from __future__ import annotations
+
+import io
+import base64
+import pathlib
+from typing import Any, Mapping, TypeVar, cast
+from datetime import date, datetime
+from typing_extensions import Literal, get_args, override, get_type_hints as _get_type_hints
+
+import anyio
+import pydantic
+
+from ._utils import (
+ is_list,
+ is_given,
+ lru_cache,
+ is_mapping,
+ is_iterable,
+ is_sequence,
+)
+from .._files import is_base64_file_input
+from ._compat import get_origin, is_typeddict
+from ._typing import (
+ is_list_type,
+ is_union_type,
+ extract_type_arg,
+ is_iterable_type,
+ is_required_type,
+ is_sequence_type,
+ is_annotated_type,
+ strip_annotated_type,
+)
+
+_T = TypeVar("_T")
+
+
+# TODO: support for drilling globals() and locals()
+# TODO: ensure works correctly with forward references in all cases
+
+
+PropertyFormat = Literal["iso8601", "base64", "custom"]
+
+
+class PropertyInfo:
+ """Metadata class to be used in Annotated types to provide information about a given type.
+
+ For example:
+
+ class MyParams(TypedDict):
+ account_holder_name: Annotated[str, PropertyInfo(alias='accountHolderName')]
+
+ This means that {'account_holder_name': 'Robert'} will be transformed to {'accountHolderName': 'Robert'} before being sent to the API.
+ """
+
+ alias: str | None
+ format: PropertyFormat | None
+ format_template: str | None
+ discriminator: str | None
+
+ def __init__(
+ self,
+ *,
+ alias: str | None = None,
+ format: PropertyFormat | None = None,
+ format_template: str | None = None,
+ discriminator: str | None = None,
+ ) -> None:
+ self.alias = alias
+ self.format = format
+ self.format_template = format_template
+ self.discriminator = discriminator
+
+ @override
+ def __repr__(self) -> str:
+ return f"{self.__class__.__name__}(alias='{self.alias}', format={self.format}, format_template='{self.format_template}', discriminator='{self.discriminator}')"
+
+
+def maybe_transform(
+ data: object,
+ expected_type: object,
+) -> Any | None:
+ """Wrapper over `transform()` that allows `None` to be passed.
+
+ See `transform()` for more details.
+ """
+ if data is None:
+ return None
+ return transform(data, expected_type)
+
+
+# Wrapper over _transform_recursive providing fake types
+def transform(
+ data: _T,
+ expected_type: object,
+) -> _T:
+ """Transform dictionaries based off of type information from the given type, for example:
+
+ ```py
+ class Params(TypedDict, total=False):
+ card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]]
+
+
+ transformed = transform({"card_id": ""}, Params)
+ # {'cardID': ''}
+ ```
+
+ Any keys / data that does not have type information given will be included as is.
+
+ It should be noted that the transformations that this function does are not represented in the type system.
+ """
+ transformed = _transform_recursive(data, annotation=cast(type, expected_type))
+ return cast(_T, transformed)
+
+
+@lru_cache(maxsize=8096)
+def _get_annotated_type(type_: type) -> type | None:
+ """If the given type is an `Annotated` type then it is returned, if not `None` is returned.
+
+ This also unwraps the type when applicable, e.g. `Required[Annotated[T, ...]]`
+ """
+ if is_required_type(type_):
+ # Unwrap `Required[Annotated[T, ...]]` to `Annotated[T, ...]`
+ type_ = get_args(type_)[0]
+
+ if is_annotated_type(type_):
+ return type_
+
+ return None
+
+
+def _maybe_transform_key(key: str, type_: type) -> str:
+ """Transform the given `data` based on the annotations provided in `type_`.
+
+ Note: this function only looks at `Annotated` types that contain `PropertyInfo` metadata.
+ """
+ annotated_type = _get_annotated_type(type_)
+ if annotated_type is None:
+ # no `Annotated` definition for this type, no transformation needed
+ return key
+
+ # ignore the first argument as it is the actual type
+ annotations = get_args(annotated_type)[1:]
+ for annotation in annotations:
+ if isinstance(annotation, PropertyInfo) and annotation.alias is not None:
+ return annotation.alias
+
+ return key
+
+
+def _no_transform_needed(annotation: type) -> bool:
+ return annotation == float or annotation == int
+
+
+def _transform_recursive(
+ data: object,
+ *,
+ annotation: type,
+ inner_type: type | None = None,
+) -> object:
+ """Transform the given data against the expected type.
+
+ Args:
+ annotation: The direct type annotation given to the particular piece of data.
+ This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc
+
+ inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type
+ is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in
+ the list can be transformed using the metadata from the container type.
+
+ Defaults to the same value as the `annotation` argument.
+ """
+ from .._compat import model_dump
+
+ if inner_type is None:
+ inner_type = annotation
+
+ stripped_type = strip_annotated_type(inner_type)
+ origin = get_origin(stripped_type) or stripped_type
+ if is_typeddict(stripped_type) and is_mapping(data):
+ return _transform_typeddict(data, stripped_type)
+
+ if origin == dict and is_mapping(data):
+ items_type = get_args(stripped_type)[1]
+ return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()}
+
+ if (
+ # List[T]
+ (is_list_type(stripped_type) and is_list(data))
+ # Iterable[T]
+ or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
+ # Sequence[T]
+ or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str))
+ ):
+ # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
+ # intended as an iterable, so we don't transform it.
+ if isinstance(data, dict):
+ return cast(object, data)
+
+ inner_type = extract_type_arg(stripped_type, 0)
+ if _no_transform_needed(inner_type):
+ # for some types there is no need to transform anything, so we can get a small
+ # perf boost from skipping that work.
+ #
+ # but we still need to convert to a list to ensure the data is json-serializable
+ if is_list(data):
+ return data
+ return list(data)
+
+ return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
+
+ if is_union_type(stripped_type):
+ # For union types we run the transformation against all subtypes to ensure that everything is transformed.
+ #
+ # TODO: there may be edge cases where the same normalized field name will transform to two different names
+ # in different subtypes.
+ for subtype in get_args(stripped_type):
+ data = _transform_recursive(data, annotation=annotation, inner_type=subtype)
+ return data
+
+ if isinstance(data, pydantic.BaseModel):
+ return model_dump(data, exclude_unset=True, mode="json")
+
+ annotated_type = _get_annotated_type(annotation)
+ if annotated_type is None:
+ return data
+
+ # ignore the first argument as it is the actual type
+ annotations = get_args(annotated_type)[1:]
+ for annotation in annotations:
+ if isinstance(annotation, PropertyInfo) and annotation.format is not None:
+ return _format_data(data, annotation.format, annotation.format_template)
+
+ return data
+
+
+def _format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object:
+ if isinstance(data, (date, datetime)):
+ if format_ == "iso8601":
+ return data.isoformat()
+
+ if format_ == "custom" and format_template is not None:
+ return data.strftime(format_template)
+
+ if format_ == "base64" and is_base64_file_input(data):
+ binary: str | bytes | None = None
+
+ if isinstance(data, pathlib.Path):
+ binary = data.read_bytes()
+ elif isinstance(data, io.IOBase):
+ binary = data.read()
+
+ if isinstance(binary, str): # type: ignore[unreachable]
+ binary = binary.encode()
+
+ if not isinstance(binary, bytes):
+ raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}")
+
+ return base64.b64encode(binary).decode("ascii")
+
+ return data
+
+
+def _transform_typeddict(
+ data: Mapping[str, object],
+ expected_type: type,
+) -> Mapping[str, object]:
+ result: dict[str, object] = {}
+ annotations = get_type_hints(expected_type, include_extras=True)
+ for key, value in data.items():
+ if not is_given(value):
+ # we don't need to include omitted values here as they'll
+ # be stripped out before the request is sent anyway
+ continue
+
+ type_ = annotations.get(key)
+ if type_ is None:
+ # we do not have a type annotation for this field, leave it as is
+ result[key] = value
+ else:
+ result[_maybe_transform_key(key, type_)] = _transform_recursive(value, annotation=type_)
+ return result
+
+
+async def async_maybe_transform(
+ data: object,
+ expected_type: object,
+) -> Any | None:
+ """Wrapper over `async_transform()` that allows `None` to be passed.
+
+ See `async_transform()` for more details.
+ """
+ if data is None:
+ return None
+ return await async_transform(data, expected_type)
+
+
+async def async_transform(
+ data: _T,
+ expected_type: object,
+) -> _T:
+ """Transform dictionaries based off of type information from the given type, for example:
+
+ ```py
+ class Params(TypedDict, total=False):
+ card_id: Required[Annotated[str, PropertyInfo(alias="cardID")]]
+
+
+ transformed = transform({"card_id": ""}, Params)
+ # {'cardID': ''}
+ ```
+
+ Any keys / data that does not have type information given will be included as is.
+
+ It should be noted that the transformations that this function does are not represented in the type system.
+ """
+ transformed = await _async_transform_recursive(data, annotation=cast(type, expected_type))
+ return cast(_T, transformed)
+
+
+async def _async_transform_recursive(
+ data: object,
+ *,
+ annotation: type,
+ inner_type: type | None = None,
+) -> object:
+ """Transform the given data against the expected type.
+
+ Args:
+ annotation: The direct type annotation given to the particular piece of data.
+ This may or may not be wrapped in metadata types, e.g. `Required[T]`, `Annotated[T, ...]` etc
+
+ inner_type: If applicable, this is the "inside" type. This is useful in certain cases where the outside type
+ is a container type such as `List[T]`. In that case `inner_type` should be set to `T` so that each entry in
+ the list can be transformed using the metadata from the container type.
+
+ Defaults to the same value as the `annotation` argument.
+ """
+ from .._compat import model_dump
+
+ if inner_type is None:
+ inner_type = annotation
+
+ stripped_type = strip_annotated_type(inner_type)
+ origin = get_origin(stripped_type) or stripped_type
+ if is_typeddict(stripped_type) and is_mapping(data):
+ return await _async_transform_typeddict(data, stripped_type)
+
+ if origin == dict and is_mapping(data):
+ items_type = get_args(stripped_type)[1]
+ return {key: _transform_recursive(value, annotation=items_type) for key, value in data.items()}
+
+ if (
+ # List[T]
+ (is_list_type(stripped_type) and is_list(data))
+ # Iterable[T]
+ or (is_iterable_type(stripped_type) and is_iterable(data) and not isinstance(data, str))
+ # Sequence[T]
+ or (is_sequence_type(stripped_type) and is_sequence(data) and not isinstance(data, str))
+ ):
+ # dicts are technically iterable, but it is an iterable on the keys of the dict and is not usually
+ # intended as an iterable, so we don't transform it.
+ if isinstance(data, dict):
+ return cast(object, data)
+
+ inner_type = extract_type_arg(stripped_type, 0)
+ if _no_transform_needed(inner_type):
+ # for some types there is no need to transform anything, so we can get a small
+ # perf boost from skipping that work.
+ #
+ # but we still need to convert to a list to ensure the data is json-serializable
+ if is_list(data):
+ return data
+ return list(data)
+
+ return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
+
+ if is_union_type(stripped_type):
+ # For union types we run the transformation against all subtypes to ensure that everything is transformed.
+ #
+ # TODO: there may be edge cases where the same normalized field name will transform to two different names
+ # in different subtypes.
+ for subtype in get_args(stripped_type):
+ data = await _async_transform_recursive(data, annotation=annotation, inner_type=subtype)
+ return data
+
+ if isinstance(data, pydantic.BaseModel):
+ return model_dump(data, exclude_unset=True, mode="json")
+
+ annotated_type = _get_annotated_type(annotation)
+ if annotated_type is None:
+ return data
+
+ # ignore the first argument as it is the actual type
+ annotations = get_args(annotated_type)[1:]
+ for annotation in annotations:
+ if isinstance(annotation, PropertyInfo) and annotation.format is not None:
+ return await _async_format_data(data, annotation.format, annotation.format_template)
+
+ return data
+
+
+async def _async_format_data(data: object, format_: PropertyFormat, format_template: str | None) -> object:
+ if isinstance(data, (date, datetime)):
+ if format_ == "iso8601":
+ return data.isoformat()
+
+ if format_ == "custom" and format_template is not None:
+ return data.strftime(format_template)
+
+ if format_ == "base64" and is_base64_file_input(data):
+ binary: str | bytes | None = None
+
+ if isinstance(data, pathlib.Path):
+ binary = await anyio.Path(data).read_bytes()
+ elif isinstance(data, io.IOBase):
+ binary = data.read()
+
+ if isinstance(binary, str): # type: ignore[unreachable]
+ binary = binary.encode()
+
+ if not isinstance(binary, bytes):
+ raise RuntimeError(f"Could not read bytes from {data}; Received {type(binary)}")
+
+ return base64.b64encode(binary).decode("ascii")
+
+ return data
+
+
+async def _async_transform_typeddict(
+ data: Mapping[str, object],
+ expected_type: type,
+) -> Mapping[str, object]:
+ result: dict[str, object] = {}
+ annotations = get_type_hints(expected_type, include_extras=True)
+ for key, value in data.items():
+ if not is_given(value):
+ # we don't need to include omitted values here as they'll
+ # be stripped out before the request is sent anyway
+ continue
+
+ type_ = annotations.get(key)
+ if type_ is None:
+ # we do not have a type annotation for this field, leave it as is
+ result[key] = value
+ else:
+ result[_maybe_transform_key(key, type_)] = await _async_transform_recursive(value, annotation=type_)
+ return result
+
+
+@lru_cache(maxsize=8096)
+def get_type_hints(
+ obj: Any,
+ globalns: dict[str, Any] | None = None,
+ localns: Mapping[str, Any] | None = None,
+ include_extras: bool = False,
+) -> dict[str, Any]:
+ return _get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras)
diff --git a/src/opencode/_utils/_typing.py b/src/opencode/_utils/_typing.py
new file mode 100644
index 0000000..193109f
--- /dev/null
+++ b/src/opencode/_utils/_typing.py
@@ -0,0 +1,156 @@
+from __future__ import annotations
+
+import sys
+import typing
+import typing_extensions
+from typing import Any, TypeVar, Iterable, cast
+from collections import abc as _c_abc
+from typing_extensions import (
+ TypeIs,
+ Required,
+ Annotated,
+ get_args,
+ get_origin,
+)
+
+from ._utils import lru_cache
+from .._types import InheritsGeneric
+from ._compat import is_union as _is_union
+
+
+def is_annotated_type(typ: type) -> bool:
+ return get_origin(typ) == Annotated
+
+
+def is_list_type(typ: type) -> bool:
+ return (get_origin(typ) or typ) == list
+
+
+def is_sequence_type(typ: type) -> bool:
+ origin = get_origin(typ) or typ
+ return origin == typing_extensions.Sequence or origin == typing.Sequence or origin == _c_abc.Sequence
+
+
+def is_iterable_type(typ: type) -> bool:
+ """If the given type is `typing.Iterable[T]`"""
+ origin = get_origin(typ) or typ
+ return origin == Iterable or origin == _c_abc.Iterable
+
+
+def is_union_type(typ: type) -> bool:
+ return _is_union(get_origin(typ))
+
+
+def is_required_type(typ: type) -> bool:
+ return get_origin(typ) == Required
+
+
+def is_typevar(typ: type) -> bool:
+ # type ignore is required because type checkers
+ # think this expression will always return False
+ return type(typ) == TypeVar # type: ignore
+
+
+_TYPE_ALIAS_TYPES: tuple[type[typing_extensions.TypeAliasType], ...] = (typing_extensions.TypeAliasType,)
+if sys.version_info >= (3, 12):
+ _TYPE_ALIAS_TYPES = (*_TYPE_ALIAS_TYPES, typing.TypeAliasType)
+
+
+def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]:
+ """Return whether the provided argument is an instance of `TypeAliasType`.
+
+ ```python
+ type Int = int
+ is_type_alias_type(Int)
+ # > True
+ Str = TypeAliasType("Str", str)
+ is_type_alias_type(Str)
+ # > True
+ ```
+ """
+ return isinstance(tp, _TYPE_ALIAS_TYPES)
+
+
+# Extracts T from Annotated[T, ...] or from Required[Annotated[T, ...]]
+@lru_cache(maxsize=8096)
+def strip_annotated_type(typ: type) -> type:
+ if is_required_type(typ) or is_annotated_type(typ):
+ return strip_annotated_type(cast(type, get_args(typ)[0]))
+
+ return typ
+
+
+def extract_type_arg(typ: type, index: int) -> type:
+ args = get_args(typ)
+ try:
+ return cast(type, args[index])
+ except IndexError as err:
+ raise RuntimeError(f"Expected type {typ} to have a type argument at index {index} but it did not") from err
+
+
+def extract_type_var_from_base(
+ typ: type,
+ *,
+ generic_bases: tuple[type, ...],
+ index: int,
+ failure_message: str | None = None,
+) -> type:
+ """Given a type like `Foo[T]`, returns the generic type variable `T`.
+
+ This also handles the case where a concrete subclass is given, e.g.
+ ```py
+ class MyResponse(Foo[bytes]):
+ ...
+
+ extract_type_var(MyResponse, bases=(Foo,), index=0) -> bytes
+ ```
+
+ And where a generic subclass is given:
+ ```py
+ _T = TypeVar('_T')
+ class MyResponse(Foo[_T]):
+ ...
+
+ extract_type_var(MyResponse[bytes], bases=(Foo,), index=0) -> bytes
+ ```
+ """
+ cls = cast(object, get_origin(typ) or typ)
+ if cls in generic_bases: # pyright: ignore[reportUnnecessaryContains]
+ # we're given the class directly
+ return extract_type_arg(typ, index)
+
+ # if a subclass is given
+ # ---
+ # this is needed as __orig_bases__ is not present in the typeshed stubs
+ # because it is intended to be for internal use only, however there does
+ # not seem to be a way to resolve generic TypeVars for inherited subclasses
+ # without using it.
+ if isinstance(cls, InheritsGeneric):
+ target_base_class: Any | None = None
+ for base in cls.__orig_bases__:
+ if base.__origin__ in generic_bases:
+ target_base_class = base
+ break
+
+ if target_base_class is None:
+ raise RuntimeError(
+ "Could not find the generic base class;\n"
+ "This should never happen;\n"
+ f"Does {cls} inherit from one of {generic_bases} ?"
+ )
+
+ extracted = extract_type_arg(target_base_class, index)
+ if is_typevar(extracted):
+ # If the extracted type argument is itself a type variable
+ # then that means the subclass itself is generic, so we have
+ # to resolve the type argument from the class itself, not
+ # the base class.
+ #
+ # Note: if there is more than 1 type argument, the subclass could
+ # change the ordering of the type arguments, this is not currently
+ # supported.
+ return extract_type_arg(typ, index)
+
+ return extracted
+
+ raise RuntimeError(failure_message or f"Could not resolve inner type variable at index {index} for {typ}")
diff --git a/src/opencode/_utils/_utils.py b/src/opencode/_utils/_utils.py
new file mode 100644
index 0000000..199cd23
--- /dev/null
+++ b/src/opencode/_utils/_utils.py
@@ -0,0 +1,433 @@
+from __future__ import annotations
+
+import os
+import re
+import inspect
+import functools
+from typing import (
+ Any,
+ Tuple,
+ Mapping,
+ TypeVar,
+ Callable,
+ Iterable,
+ Sequence,
+ cast,
+ overload,
+)
+from pathlib import Path
+from datetime import date, datetime
+from typing_extensions import TypeGuard, get_args
+
+import sniffio
+
+from .._types import Omit, NotGiven, FileTypes, ArrayFormat, HeadersLike
+
+_T = TypeVar("_T")
+_TupleT = TypeVar("_TupleT", bound=Tuple[object, ...])
+_MappingT = TypeVar("_MappingT", bound=Mapping[str, object])
+_SequenceT = TypeVar("_SequenceT", bound=Sequence[object])
+CallableT = TypeVar("CallableT", bound=Callable[..., Any])
+
+
+def flatten(t: Iterable[Iterable[_T]]) -> list[_T]:
+ return [item for sublist in t for item in sublist]
+
+
+def extract_files(
+ # TODO: this needs to take Dict but variance issues.....
+ # create protocol type ?
+ query: Mapping[str, object],
+ *,
+ paths: Sequence[Sequence[str]],
+ array_format: ArrayFormat = "brackets",
+) -> list[tuple[str, FileTypes]]:
+ """Recursively extract files from the given dictionary based on specified paths.
+
+ A path may look like this ['foo', 'files', '', 'data'].
+
+ ``array_format`` controls how ```` segments contribute to the emitted
+ field name. Supported values: ``"brackets"`` (``foo[]``), ``"repeat"`` and
+ ``"comma"`` (``foo``), ``"indices"`` (``foo[0]``, ``foo[1]``).
+
+ Note: this mutates the given dictionary.
+ """
+ files: list[tuple[str, FileTypes]] = []
+ for path in paths:
+ files.extend(_extract_items(query, path, index=0, flattened_key=None, array_format=array_format))
+ return files
+
+
+def _array_suffix(array_format: ArrayFormat, array_index: int) -> str:
+ if array_format == "brackets":
+ return "[]"
+ if array_format == "indices":
+ return f"[{array_index}]"
+ if array_format == "repeat" or array_format == "comma":
+ # Both repeat the bare field name for each file part; there is no
+ # meaningful way to comma-join binary parts.
+ return ""
+ raise NotImplementedError(
+ f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}"
+ )
+
+
+def _extract_items(
+ obj: object,
+ path: Sequence[str],
+ *,
+ index: int,
+ flattened_key: str | None,
+ array_format: ArrayFormat,
+) -> list[tuple[str, FileTypes]]:
+ try:
+ key = path[index]
+ except IndexError:
+ if not is_given(obj):
+ # no value was provided - we can safely ignore
+ return []
+
+ # cyclical import
+ from .._files import assert_is_file_content
+
+ # We have exhausted the path, return the entry we found.
+ assert flattened_key is not None
+
+ if is_list(obj):
+ files: list[tuple[str, FileTypes]] = []
+ for array_index, entry in enumerate(obj):
+ suffix = _array_suffix(array_format, array_index)
+ emitted_key = (flattened_key + suffix) if flattened_key else suffix
+ assert_is_file_content(entry, key=emitted_key)
+ files.append((emitted_key, cast(FileTypes, entry)))
+ return files
+
+ assert_is_file_content(obj, key=flattened_key)
+ return [(flattened_key, cast(FileTypes, obj))]
+
+ index += 1
+ if is_dict(obj):
+ try:
+ # Remove the field if there are no more dict keys in the path,
+ # only "" traversal markers or end.
+ if all(p == "" for p in path[index:]):
+ item = obj.pop(key)
+ else:
+ item = obj[key]
+ except KeyError:
+ # Key was not present in the dictionary, this is not indicative of an error
+ # as the given path may not point to a required field. We also do not want
+ # to enforce required fields as the API may differ from the spec in some cases.
+ return []
+ if flattened_key is None:
+ flattened_key = key
+ else:
+ flattened_key += f"[{key}]"
+ return _extract_items(
+ item,
+ path,
+ index=index,
+ flattened_key=flattened_key,
+ array_format=array_format,
+ )
+ elif is_list(obj):
+ if key != "":
+ return []
+
+ return flatten(
+ [
+ _extract_items(
+ item,
+ path,
+ index=index,
+ flattened_key=(
+ (flattened_key if flattened_key is not None else "") + _array_suffix(array_format, array_index)
+ ),
+ array_format=array_format,
+ )
+ for array_index, item in enumerate(obj)
+ ]
+ )
+
+ # Something unexpected was passed, just ignore it.
+ return []
+
+
+def is_given(obj: _T | NotGiven | Omit) -> TypeGuard[_T]:
+ return not isinstance(obj, NotGiven) and not isinstance(obj, Omit)
+
+
+# Type safe methods for narrowing types with TypeVars.
+# The default narrowing for isinstance(obj, dict) is dict[unknown, unknown],
+# however this cause Pyright to rightfully report errors. As we know we don't
+# care about the contained types we can safely use `object` in its place.
+#
+# There are two separate functions defined, `is_*` and `is_*_t` for different use cases.
+# `is_*` is for when you're dealing with an unknown input
+# `is_*_t` is for when you're narrowing a known union type to a specific subset
+
+
+def is_tuple(obj: object) -> TypeGuard[tuple[object, ...]]:
+ return isinstance(obj, tuple)
+
+
+def is_tuple_t(obj: _TupleT | object) -> TypeGuard[_TupleT]:
+ return isinstance(obj, tuple)
+
+
+def is_sequence(obj: object) -> TypeGuard[Sequence[object]]:
+ return isinstance(obj, Sequence)
+
+
+def is_sequence_t(obj: _SequenceT | object) -> TypeGuard[_SequenceT]:
+ return isinstance(obj, Sequence)
+
+
+def is_mapping(obj: object) -> TypeGuard[Mapping[str, object]]:
+ return isinstance(obj, Mapping)
+
+
+def is_mapping_t(obj: _MappingT | object) -> TypeGuard[_MappingT]:
+ return isinstance(obj, Mapping)
+
+
+def is_dict(obj: object) -> TypeGuard[dict[object, object]]:
+ return isinstance(obj, dict)
+
+
+def is_list(obj: object) -> TypeGuard[list[object]]:
+ return isinstance(obj, list)
+
+
+def is_iterable(obj: object) -> TypeGuard[Iterable[object]]:
+ return isinstance(obj, Iterable)
+
+
+# copied from https://github.com/Rapptz/RoboDanny
+def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str:
+ size = len(seq)
+ if size == 0:
+ return ""
+
+ if size == 1:
+ return seq[0]
+
+ if size == 2:
+ return f"{seq[0]} {final} {seq[1]}"
+
+ return delim.join(seq[:-1]) + f" {final} {seq[-1]}"
+
+
+def quote(string: str) -> str:
+ """Add single quotation marks around the given string. Does *not* do any escaping."""
+ return f"'{string}'"
+
+
+def required_args(*variants: Sequence[str]) -> Callable[[CallableT], CallableT]:
+ """Decorator to enforce a given set of arguments or variants of arguments are passed to the decorated function.
+
+ Useful for enforcing runtime validation of overloaded functions.
+
+ Example usage:
+ ```py
+ @overload
+ def foo(*, a: str) -> str: ...
+
+
+ @overload
+ def foo(*, b: bool) -> str: ...
+
+
+ # This enforces the same constraints that a static type checker would
+ # i.e. that either a or b must be passed to the function
+ @required_args(["a"], ["b"])
+ def foo(*, a: str | None = None, b: bool | None = None) -> str: ...
+ ```
+ """
+
+ def inner(func: CallableT) -> CallableT:
+ params = inspect.signature(func).parameters
+ positional = [
+ name
+ for name, param in params.items()
+ if param.kind
+ in {
+ param.POSITIONAL_ONLY,
+ param.POSITIONAL_OR_KEYWORD,
+ }
+ ]
+
+ @functools.wraps(func)
+ def wrapper(*args: object, **kwargs: object) -> object:
+ given_params: set[str] = set()
+ for i, _ in enumerate(args):
+ try:
+ given_params.add(positional[i])
+ except IndexError:
+ raise TypeError(
+ f"{func.__name__}() takes {len(positional)} argument(s) but {len(args)} were given"
+ ) from None
+
+ for key in kwargs.keys():
+ given_params.add(key)
+
+ for variant in variants:
+ matches = all((param in given_params for param in variant))
+ if matches:
+ break
+ else: # no break
+ if len(variants) > 1:
+ variations = human_join(
+ ["(" + human_join([quote(arg) for arg in variant], final="and") + ")" for variant in variants]
+ )
+ msg = f"Missing required arguments; Expected either {variations} arguments to be given"
+ else:
+ assert len(variants) > 0
+
+ # TODO: this error message is not deterministic
+ missing = list(set(variants[0]) - given_params)
+ if len(missing) > 1:
+ msg = f"Missing required arguments: {human_join([quote(arg) for arg in missing])}"
+ else:
+ msg = f"Missing required argument: {quote(missing[0])}"
+ raise TypeError(msg)
+ return func(*args, **kwargs)
+
+ return wrapper # type: ignore
+
+ return inner
+
+
+_K = TypeVar("_K")
+_V = TypeVar("_V")
+
+
+@overload
+def strip_not_given(obj: None) -> None: ...
+
+
+@overload
+def strip_not_given(obj: Mapping[_K, _V | NotGiven]) -> dict[_K, _V]: ...
+
+
+@overload
+def strip_not_given(obj: object) -> object: ...
+
+
+def strip_not_given(obj: object | None) -> object:
+ """Remove all top-level keys where their values are instances of `NotGiven`"""
+ if obj is None:
+ return None
+
+ if not is_mapping(obj):
+ return obj
+
+ return {key: value for key, value in obj.items() if not isinstance(value, NotGiven)}
+
+
+def coerce_integer(val: str) -> int:
+ return int(val, base=10)
+
+
+def coerce_float(val: str) -> float:
+ return float(val)
+
+
+def coerce_boolean(val: str) -> bool:
+ return val == "true" or val == "1" or val == "on"
+
+
+def maybe_coerce_integer(val: str | None) -> int | None:
+ if val is None:
+ return None
+ return coerce_integer(val)
+
+
+def maybe_coerce_float(val: str | None) -> float | None:
+ if val is None:
+ return None
+ return coerce_float(val)
+
+
+def maybe_coerce_boolean(val: str | None) -> bool | None:
+ if val is None:
+ return None
+ return coerce_boolean(val)
+
+
+def removeprefix(string: str, prefix: str) -> str:
+ """Remove a prefix from a string.
+
+ Backport of `str.removeprefix` for Python < 3.9
+ """
+ if string.startswith(prefix):
+ return string[len(prefix) :]
+ return string
+
+
+def removesuffix(string: str, suffix: str) -> str:
+ """Remove a suffix from a string.
+
+ Backport of `str.removesuffix` for Python < 3.9
+ """
+ if string.endswith(suffix):
+ return string[: -len(suffix)]
+ return string
+
+
+def file_from_path(path: str) -> FileTypes:
+ contents = Path(path).read_bytes()
+ file_name = os.path.basename(path)
+ return (file_name, contents)
+
+
+def get_required_header(headers: HeadersLike, header: str) -> str:
+ lower_header = header.lower()
+ if is_mapping_t(headers):
+ # mypy doesn't understand the type narrowing here
+ for k, v in headers.items(): # type: ignore
+ if k.lower() == lower_header and isinstance(v, str):
+ return v
+
+ # to deal with the case where the header looks like Stainless-Event-Id
+ intercaps_header = re.sub(r"([^\w])(\w)", lambda pat: pat.group(1) + pat.group(2).upper(), header.capitalize())
+
+ for normalized_header in [header, lower_header, header.upper(), intercaps_header]:
+ value = headers.get(normalized_header)
+ if value:
+ return value
+
+ raise ValueError(f"Could not find {header} header")
+
+
+def get_async_library() -> str:
+ try:
+ return sniffio.current_async_library()
+ except Exception:
+ return "false"
+
+
+def lru_cache(*, maxsize: int | None = 128) -> Callable[[CallableT], CallableT]:
+ """A version of functools.lru_cache that retains the type signature
+ for the wrapped function arguments.
+ """
+ wrapper = functools.lru_cache( # noqa: TID251
+ maxsize=maxsize,
+ )
+ return cast(Any, wrapper) # type: ignore[no-any-return]
+
+
+def json_safe(data: object) -> object:
+ """Translates a mapping / sequence recursively in the same fashion
+ as `pydantic` v2's `model_dump(mode="json")`.
+ """
+ if is_mapping(data):
+ return {json_safe(key): json_safe(value) for key, value in data.items()}
+
+ if is_iterable(data) and not isinstance(data, (str, bytes, bytearray)):
+ return [json_safe(item) for item in data]
+
+ if isinstance(data, (datetime, date)):
+ return data.isoformat()
+
+ return data
diff --git a/src/opencode/pagination.py b/src/opencode/pagination.py
new file mode 100644
index 0000000..dc21769
--- /dev/null
+++ b/src/opencode/pagination.py
@@ -0,0 +1,61 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Generic, TypeVar, Optional
+from typing_extensions import override
+
+from ._models import BaseModel
+from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage
+
+__all__ = ["CursorPageCursor", "SyncCursorPage", "AsyncCursorPage"]
+
+_T = TypeVar("_T")
+
+
+class CursorPageCursor(BaseModel):
+ next: Optional[str] = None
+
+
+class SyncCursorPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+ data: List[_T]
+ cursor: Optional[CursorPageCursor] = None
+
+ @override
+ def _get_page_items(self) -> List[_T]:
+ data = self.data
+ if not data:
+ return []
+ return data
+
+ @override
+ def next_page_info(self) -> Optional[PageInfo]:
+ next = None
+ if self.cursor is not None:
+ if self.cursor.next is not None:
+ next = self.cursor.next
+ if not next:
+ return None
+
+ return PageInfo(params={"cursor": next})
+
+
+class AsyncCursorPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+ data: List[_T]
+ cursor: Optional[CursorPageCursor] = None
+
+ @override
+ def _get_page_items(self) -> List[_T]:
+ data = self.data
+ if not data:
+ return []
+ return data
+
+ @override
+ def next_page_info(self) -> Optional[PageInfo]:
+ next = None
+ if self.cursor is not None:
+ if self.cursor.next is not None:
+ next = self.cursor.next
+ if not next:
+ return None
+
+ return PageInfo(params={"cursor": next})
diff --git a/src/opencode/resources/__init__.py b/src/opencode/resources/__init__.py
index 5db651e..258cf0a 100644
--- a/src/opencode/resources/__init__.py
+++ b/src/opencode/resources/__init__.py
@@ -1,364 +1,439 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""Resource groups attached to the clients."""
-
-from __future__ import annotations
-
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
-
-from .agent import AgentResource, AsyncAgentResource
-from .command import AsyncCommandResource, CommandResource
-from .config import AsyncConfigResource, ConfigResource
-from .credential import AsyncCredentialResource, CredentialResource
-from .debug import AsyncDebugResource, DebugResource
-from .event import AsyncEventResource, EventResource
-from .experimental import AsyncExperimentalResource, ExperimentalResource
-from .file import AsyncFileResource, FileResource
-from .form import AsyncFormResource, FormResource
-from .generate import AsyncGenerateResource, GenerateResource
-from .integration import AsyncIntegrationResource, IntegrationResource
-from .location import AsyncLocationResource, LocationResource
-from .mcp import AsyncMcpResource, McpResource
-from .message import AsyncMessageResource, MessageResource
-from .migration import AsyncMigrationResource, MigrationResource
-from .model import AsyncModelResource, ModelResource
-from .permission import AsyncPermissionResource, PermissionResource
-from .plugin import AsyncPluginResource, PluginResource
-from .project import AsyncProjectResource, ProjectResource
-from .provider import AsyncProviderResource, ProviderResource
-from .pty import AsyncPtyResource, PtyResource
-from .reference import AsyncReferenceResource, ReferenceResource
-from .rpc import AsyncRpcResource, RpcResource
-from .server import AsyncServerResource, ServerResource
-from .session import AsyncSessionResource, SessionResource
-from .shell import AsyncShellResource, ShellResource
-from .skill import AsyncSkillResource, SkillResource
-from .vcs import AsyncVcsResource, VcsResource
-from .voice import AsyncVoiceResource, VoiceResource
-from .websearch import AsyncWebsearchResource, WebsearchResource
-from .worktree import AsyncWorktreeResource, WorktreeResource
-
-if TYPE_CHECKING:
- from .._client import AsyncOpencode, Opencode
-
-__all__ = ["AsyncResources", "SyncResources"]
-
-
-class SyncResources:
- """Resource groups of :class:`opencode.Opencode`."""
-
- @cached_property
- def agent(self) -> AgentResource:
- """Operations for OpenCode ``agent``."""
- return AgentResource(cast("Opencode", self))
-
- @cached_property
- def command(self) -> CommandResource:
- """Operations for OpenCode ``command``."""
- return CommandResource(cast("Opencode", self))
-
- @cached_property
- def config(self) -> ConfigResource:
- """Operations for OpenCode ``config``."""
- return ConfigResource(cast("Opencode", self))
-
- @cached_property
- def credential(self) -> CredentialResource:
- """Operations for OpenCode ``credential``."""
- return CredentialResource(cast("Opencode", self))
-
- @cached_property
- def debug(self) -> DebugResource:
- """Operations for OpenCode ``debug``."""
- return DebugResource(cast("Opencode", self))
-
- @cached_property
- def event(self) -> EventResource:
- """Operations for OpenCode ``event``."""
- return EventResource(cast("Opencode", self))
-
- @cached_property
- def experimental(self) -> ExperimentalResource:
- """Operations for OpenCode ``experimental``."""
- return ExperimentalResource(cast("Opencode", self))
-
- @cached_property
- def file(self) -> FileResource:
- """Operations for OpenCode ``file``."""
- return FileResource(cast("Opencode", self))
-
- @cached_property
- def form(self) -> FormResource:
- """Operations for OpenCode ``form``."""
- return FormResource(cast("Opencode", self))
-
- @cached_property
- def generate(self) -> GenerateResource:
- """Operations for OpenCode ``generate``."""
- return GenerateResource(cast("Opencode", self))
-
- @cached_property
- def integration(self) -> IntegrationResource:
- """Operations for OpenCode ``integration``."""
- return IntegrationResource(cast("Opencode", self))
-
- @cached_property
- def location(self) -> LocationResource:
- """Operations for OpenCode ``location``."""
- return LocationResource(cast("Opencode", self))
-
- @cached_property
- def mcp(self) -> McpResource:
- """Operations for OpenCode ``mcp``."""
- return McpResource(cast("Opencode", self))
-
- @cached_property
- def message(self) -> MessageResource:
- """Operations for OpenCode ``message``."""
- return MessageResource(cast("Opencode", self))
-
- @cached_property
- def migration(self) -> MigrationResource:
- """Operations for OpenCode ``migration``."""
- return MigrationResource(cast("Opencode", self))
-
- @cached_property
- def model(self) -> ModelResource:
- """Operations for OpenCode ``model``."""
- return ModelResource(cast("Opencode", self))
-
- @cached_property
- def permission(self) -> PermissionResource:
- """Operations for OpenCode ``permission``."""
- return PermissionResource(cast("Opencode", self))
-
- @cached_property
- def plugin(self) -> PluginResource:
- """Operations for OpenCode ``plugin``."""
- return PluginResource(cast("Opencode", self))
-
- @cached_property
- def project(self) -> ProjectResource:
- """Operations for OpenCode ``project``."""
- return ProjectResource(cast("Opencode", self))
-
- @cached_property
- def provider(self) -> ProviderResource:
- """Operations for OpenCode ``provider``."""
- return ProviderResource(cast("Opencode", self))
-
- @cached_property
- def pty(self) -> PtyResource:
- """Operations for OpenCode ``pty``."""
- return PtyResource(cast("Opencode", self))
-
- @cached_property
- def reference(self) -> ReferenceResource:
- """Operations for OpenCode ``reference``."""
- return ReferenceResource(cast("Opencode", self))
-
- @cached_property
- def rpc(self) -> RpcResource:
- """Operations for OpenCode ``rpc``."""
- return RpcResource(cast("Opencode", self))
-
- @cached_property
- def server(self) -> ServerResource:
- """Operations for OpenCode ``server``."""
- return ServerResource(cast("Opencode", self))
-
- @cached_property
- def session(self) -> SessionResource:
- """Operations for OpenCode ``session``."""
- return SessionResource(cast("Opencode", self))
-
- @cached_property
- def shell(self) -> ShellResource:
- """Operations for OpenCode ``shell``."""
- return ShellResource(cast("Opencode", self))
-
- @cached_property
- def skill(self) -> SkillResource:
- """Operations for OpenCode ``skill``."""
- return SkillResource(cast("Opencode", self))
-
- @cached_property
- def vcs(self) -> VcsResource:
- """Operations for OpenCode ``vcs``."""
- return VcsResource(cast("Opencode", self))
-
- @cached_property
- def voice(self) -> VoiceResource:
- """Operations for OpenCode ``voice``."""
- return VoiceResource(cast("Opencode", self))
-
- @cached_property
- def websearch(self) -> WebsearchResource:
- """Operations for OpenCode ``websearch``."""
- return WebsearchResource(cast("Opencode", self))
-
- @cached_property
- def worktree(self) -> WorktreeResource:
- """Operations for OpenCode ``worktree``."""
- return WorktreeResource(cast("Opencode", self))
-
-
-class AsyncResources:
- """Resource groups of :class:`opencode.AsyncOpencode`."""
-
- @cached_property
- def agent(self) -> AsyncAgentResource:
- """Operations for OpenCode ``agent``."""
- return AsyncAgentResource(cast("AsyncOpencode", self))
-
- @cached_property
- def command(self) -> AsyncCommandResource:
- """Operations for OpenCode ``command``."""
- return AsyncCommandResource(cast("AsyncOpencode", self))
-
- @cached_property
- def config(self) -> AsyncConfigResource:
- """Operations for OpenCode ``config``."""
- return AsyncConfigResource(cast("AsyncOpencode", self))
-
- @cached_property
- def credential(self) -> AsyncCredentialResource:
- """Operations for OpenCode ``credential``."""
- return AsyncCredentialResource(cast("AsyncOpencode", self))
-
- @cached_property
- def debug(self) -> AsyncDebugResource:
- """Operations for OpenCode ``debug``."""
- return AsyncDebugResource(cast("AsyncOpencode", self))
-
- @cached_property
- def event(self) -> AsyncEventResource:
- """Operations for OpenCode ``event``."""
- return AsyncEventResource(cast("AsyncOpencode", self))
-
- @cached_property
- def experimental(self) -> AsyncExperimentalResource:
- """Operations for OpenCode ``experimental``."""
- return AsyncExperimentalResource(cast("AsyncOpencode", self))
-
- @cached_property
- def file(self) -> AsyncFileResource:
- """Operations for OpenCode ``file``."""
- return AsyncFileResource(cast("AsyncOpencode", self))
-
- @cached_property
- def form(self) -> AsyncFormResource:
- """Operations for OpenCode ``form``."""
- return AsyncFormResource(cast("AsyncOpencode", self))
-
- @cached_property
- def generate(self) -> AsyncGenerateResource:
- """Operations for OpenCode ``generate``."""
- return AsyncGenerateResource(cast("AsyncOpencode", self))
-
- @cached_property
- def integration(self) -> AsyncIntegrationResource:
- """Operations for OpenCode ``integration``."""
- return AsyncIntegrationResource(cast("AsyncOpencode", self))
-
- @cached_property
- def location(self) -> AsyncLocationResource:
- """Operations for OpenCode ``location``."""
- return AsyncLocationResource(cast("AsyncOpencode", self))
-
- @cached_property
- def mcp(self) -> AsyncMcpResource:
- """Operations for OpenCode ``mcp``."""
- return AsyncMcpResource(cast("AsyncOpencode", self))
-
- @cached_property
- def message(self) -> AsyncMessageResource:
- """Operations for OpenCode ``message``."""
- return AsyncMessageResource(cast("AsyncOpencode", self))
-
- @cached_property
- def migration(self) -> AsyncMigrationResource:
- """Operations for OpenCode ``migration``."""
- return AsyncMigrationResource(cast("AsyncOpencode", self))
-
- @cached_property
- def model(self) -> AsyncModelResource:
- """Operations for OpenCode ``model``."""
- return AsyncModelResource(cast("AsyncOpencode", self))
-
- @cached_property
- def permission(self) -> AsyncPermissionResource:
- """Operations for OpenCode ``permission``."""
- return AsyncPermissionResource(cast("AsyncOpencode", self))
-
- @cached_property
- def plugin(self) -> AsyncPluginResource:
- """Operations for OpenCode ``plugin``."""
- return AsyncPluginResource(cast("AsyncOpencode", self))
-
- @cached_property
- def project(self) -> AsyncProjectResource:
- """Operations for OpenCode ``project``."""
- return AsyncProjectResource(cast("AsyncOpencode", self))
-
- @cached_property
- def provider(self) -> AsyncProviderResource:
- """Operations for OpenCode ``provider``."""
- return AsyncProviderResource(cast("AsyncOpencode", self))
-
- @cached_property
- def pty(self) -> AsyncPtyResource:
- """Operations for OpenCode ``pty``."""
- return AsyncPtyResource(cast("AsyncOpencode", self))
-
- @cached_property
- def reference(self) -> AsyncReferenceResource:
- """Operations for OpenCode ``reference``."""
- return AsyncReferenceResource(cast("AsyncOpencode", self))
-
- @cached_property
- def rpc(self) -> AsyncRpcResource:
- """Operations for OpenCode ``rpc``."""
- return AsyncRpcResource(cast("AsyncOpencode", self))
-
- @cached_property
- def server(self) -> AsyncServerResource:
- """Operations for OpenCode ``server``."""
- return AsyncServerResource(cast("AsyncOpencode", self))
-
- @cached_property
- def session(self) -> AsyncSessionResource:
- """Operations for OpenCode ``session``."""
- return AsyncSessionResource(cast("AsyncOpencode", self))
-
- @cached_property
- def shell(self) -> AsyncShellResource:
- """Operations for OpenCode ``shell``."""
- return AsyncShellResource(cast("AsyncOpencode", self))
-
- @cached_property
- def skill(self) -> AsyncSkillResource:
- """Operations for OpenCode ``skill``."""
- return AsyncSkillResource(cast("AsyncOpencode", self))
-
- @cached_property
- def vcs(self) -> AsyncVcsResource:
- """Operations for OpenCode ``vcs``."""
- return AsyncVcsResource(cast("AsyncOpencode", self))
-
- @cached_property
- def voice(self) -> AsyncVoiceResource:
- """Operations for OpenCode ``voice``."""
- return AsyncVoiceResource(cast("AsyncOpencode", self))
-
- @cached_property
- def websearch(self) -> AsyncWebsearchResource:
- """Operations for OpenCode ``websearch``."""
- return AsyncWebsearchResource(cast("AsyncOpencode", self))
-
- @cached_property
- def worktree(self) -> AsyncWorktreeResource:
- """Operations for OpenCode ``worktree``."""
- return AsyncWorktreeResource(cast("AsyncOpencode", self))
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from .mcp import (
+ McpResource,
+ AsyncMcpResource,
+ McpResourceWithRawResponse,
+ AsyncMcpResourceWithRawResponse,
+ McpResourceWithStreamingResponse,
+ AsyncMcpResourceWithStreamingResponse,
+)
+from .pty import (
+ PtyResource,
+ AsyncPtyResource,
+ PtyResourceWithRawResponse,
+ AsyncPtyResourceWithRawResponse,
+ PtyResourceWithStreamingResponse,
+ AsyncPtyResourceWithStreamingResponse,
+)
+from .rpc import (
+ RpcResource,
+ AsyncRpcResource,
+ RpcResourceWithRawResponse,
+ AsyncRpcResourceWithRawResponse,
+ RpcResourceWithStreamingResponse,
+ AsyncRpcResourceWithStreamingResponse,
+)
+from .vcs import (
+ VcsResource,
+ AsyncVcsResource,
+ VcsResourceWithRawResponse,
+ AsyncVcsResourceWithRawResponse,
+ VcsResourceWithStreamingResponse,
+ AsyncVcsResourceWithStreamingResponse,
+)
+from .file import (
+ FileResource,
+ AsyncFileResource,
+ FileResourceWithRawResponse,
+ AsyncFileResourceWithRawResponse,
+ FileResourceWithStreamingResponse,
+ AsyncFileResourceWithStreamingResponse,
+)
+from .form import (
+ FormResource,
+ AsyncFormResource,
+ FormResourceWithRawResponse,
+ AsyncFormResourceWithRawResponse,
+ FormResourceWithStreamingResponse,
+ AsyncFormResourceWithStreamingResponse,
+)
+from .agent import (
+ AgentResource,
+ AsyncAgentResource,
+ AgentResourceWithRawResponse,
+ AsyncAgentResourceWithRawResponse,
+ AgentResourceWithStreamingResponse,
+ AsyncAgentResourceWithStreamingResponse,
+)
+from .debug import (
+ DebugResource,
+ AsyncDebugResource,
+ DebugResourceWithRawResponse,
+ AsyncDebugResourceWithRawResponse,
+ DebugResourceWithStreamingResponse,
+ AsyncDebugResourceWithStreamingResponse,
+)
+from .event import (
+ EventResource,
+ AsyncEventResource,
+ EventResourceWithRawResponse,
+ AsyncEventResourceWithRawResponse,
+ EventResourceWithStreamingResponse,
+ AsyncEventResourceWithStreamingResponse,
+)
+from .model import (
+ ModelResource,
+ AsyncModelResource,
+ ModelResourceWithRawResponse,
+ AsyncModelResourceWithRawResponse,
+ ModelResourceWithStreamingResponse,
+ AsyncModelResourceWithStreamingResponse,
+)
+from .shell import (
+ ShellResource,
+ AsyncShellResource,
+ ShellResourceWithRawResponse,
+ AsyncShellResourceWithRawResponse,
+ ShellResourceWithStreamingResponse,
+ AsyncShellResourceWithStreamingResponse,
+)
+from .skill import (
+ SkillResource,
+ AsyncSkillResource,
+ SkillResourceWithRawResponse,
+ AsyncSkillResourceWithRawResponse,
+ SkillResourceWithStreamingResponse,
+ AsyncSkillResourceWithStreamingResponse,
+)
+from .voice import (
+ VoiceResource,
+ AsyncVoiceResource,
+ VoiceResourceWithRawResponse,
+ AsyncVoiceResourceWithRawResponse,
+ VoiceResourceWithStreamingResponse,
+ AsyncVoiceResourceWithStreamingResponse,
+)
+from .config import (
+ ConfigResource,
+ AsyncConfigResource,
+ ConfigResourceWithRawResponse,
+ AsyncConfigResourceWithRawResponse,
+ ConfigResourceWithStreamingResponse,
+ AsyncConfigResourceWithStreamingResponse,
+)
+from .plugin import (
+ PluginResource,
+ AsyncPluginResource,
+ PluginResourceWithRawResponse,
+ AsyncPluginResourceWithRawResponse,
+ PluginResourceWithStreamingResponse,
+ AsyncPluginResourceWithStreamingResponse,
+)
+from .server import (
+ ServerResource,
+ AsyncServerResource,
+ ServerResourceWithRawResponse,
+ AsyncServerResourceWithRawResponse,
+ ServerResourceWithStreamingResponse,
+ AsyncServerResourceWithStreamingResponse,
+)
+from .command import (
+ CommandResource,
+ AsyncCommandResource,
+ CommandResourceWithRawResponse,
+ AsyncCommandResourceWithRawResponse,
+ CommandResourceWithStreamingResponse,
+ AsyncCommandResourceWithStreamingResponse,
+)
+from .message import (
+ MessageResource,
+ AsyncMessageResource,
+ MessageResourceWithRawResponse,
+ AsyncMessageResourceWithRawResponse,
+ MessageResourceWithStreamingResponse,
+ AsyncMessageResourceWithStreamingResponse,
+)
+from .project import (
+ ProjectResource,
+ AsyncProjectResource,
+ ProjectResourceWithRawResponse,
+ AsyncProjectResourceWithRawResponse,
+ ProjectResourceWithStreamingResponse,
+ AsyncProjectResourceWithStreamingResponse,
+)
+from .session import (
+ SessionResource,
+ AsyncSessionResource,
+ SessionResourceWithRawResponse,
+ AsyncSessionResourceWithRawResponse,
+ SessionResourceWithStreamingResponse,
+ AsyncSessionResourceWithStreamingResponse,
+)
+from .generate import (
+ GenerateResource,
+ AsyncGenerateResource,
+ GenerateResourceWithRawResponse,
+ AsyncGenerateResourceWithRawResponse,
+ GenerateResourceWithStreamingResponse,
+ AsyncGenerateResourceWithStreamingResponse,
+)
+from .location import (
+ LocationResource,
+ AsyncLocationResource,
+ LocationResourceWithRawResponse,
+ AsyncLocationResourceWithRawResponse,
+ LocationResourceWithStreamingResponse,
+ AsyncLocationResourceWithStreamingResponse,
+)
+from .provider import (
+ ProviderResource,
+ AsyncProviderResource,
+ ProviderResourceWithRawResponse,
+ AsyncProviderResourceWithRawResponse,
+ ProviderResourceWithStreamingResponse,
+ AsyncProviderResourceWithStreamingResponse,
+)
+from .worktree import (
+ WorktreeResource,
+ AsyncWorktreeResource,
+ WorktreeResourceWithRawResponse,
+ AsyncWorktreeResourceWithRawResponse,
+ WorktreeResourceWithStreamingResponse,
+ AsyncWorktreeResourceWithStreamingResponse,
+)
+from .migration import (
+ MigrationResource,
+ AsyncMigrationResource,
+ MigrationResourceWithRawResponse,
+ AsyncMigrationResourceWithRawResponse,
+ MigrationResourceWithStreamingResponse,
+ AsyncMigrationResourceWithStreamingResponse,
+)
+from .reference import (
+ ReferenceResource,
+ AsyncReferenceResource,
+ ReferenceResourceWithRawResponse,
+ AsyncReferenceResourceWithRawResponse,
+ ReferenceResourceWithStreamingResponse,
+ AsyncReferenceResourceWithStreamingResponse,
+)
+from .websearch import (
+ WebsearchResource,
+ AsyncWebsearchResource,
+ WebsearchResourceWithRawResponse,
+ AsyncWebsearchResourceWithRawResponse,
+ WebsearchResourceWithStreamingResponse,
+ AsyncWebsearchResourceWithStreamingResponse,
+)
+from .credential import (
+ CredentialResource,
+ AsyncCredentialResource,
+ CredentialResourceWithRawResponse,
+ AsyncCredentialResourceWithRawResponse,
+ CredentialResourceWithStreamingResponse,
+ AsyncCredentialResourceWithStreamingResponse,
+)
+from .permission import (
+ PermissionResource,
+ AsyncPermissionResource,
+ PermissionResourceWithRawResponse,
+ AsyncPermissionResourceWithRawResponse,
+ PermissionResourceWithStreamingResponse,
+ AsyncPermissionResourceWithStreamingResponse,
+)
+from .integration import (
+ IntegrationResource,
+ AsyncIntegrationResource,
+ IntegrationResourceWithRawResponse,
+ AsyncIntegrationResourceWithRawResponse,
+ IntegrationResourceWithStreamingResponse,
+ AsyncIntegrationResourceWithStreamingResponse,
+)
+from .experimental import (
+ ExperimentalResource,
+ AsyncExperimentalResource,
+ ExperimentalResourceWithRawResponse,
+ AsyncExperimentalResourceWithRawResponse,
+ ExperimentalResourceWithStreamingResponse,
+ AsyncExperimentalResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "AgentResource",
+ "AsyncAgentResource",
+ "AgentResourceWithRawResponse",
+ "AsyncAgentResourceWithRawResponse",
+ "AgentResourceWithStreamingResponse",
+ "AsyncAgentResourceWithStreamingResponse",
+ "CommandResource",
+ "AsyncCommandResource",
+ "CommandResourceWithRawResponse",
+ "AsyncCommandResourceWithRawResponse",
+ "CommandResourceWithStreamingResponse",
+ "AsyncCommandResourceWithStreamingResponse",
+ "ConfigResource",
+ "AsyncConfigResource",
+ "ConfigResourceWithRawResponse",
+ "AsyncConfigResourceWithRawResponse",
+ "ConfigResourceWithStreamingResponse",
+ "AsyncConfigResourceWithStreamingResponse",
+ "CredentialResource",
+ "AsyncCredentialResource",
+ "CredentialResourceWithRawResponse",
+ "AsyncCredentialResourceWithRawResponse",
+ "CredentialResourceWithStreamingResponse",
+ "AsyncCredentialResourceWithStreamingResponse",
+ "DebugResource",
+ "AsyncDebugResource",
+ "DebugResourceWithRawResponse",
+ "AsyncDebugResourceWithRawResponse",
+ "DebugResourceWithStreamingResponse",
+ "AsyncDebugResourceWithStreamingResponse",
+ "EventResource",
+ "AsyncEventResource",
+ "EventResourceWithRawResponse",
+ "AsyncEventResourceWithRawResponse",
+ "EventResourceWithStreamingResponse",
+ "AsyncEventResourceWithStreamingResponse",
+ "ExperimentalResource",
+ "AsyncExperimentalResource",
+ "ExperimentalResourceWithRawResponse",
+ "AsyncExperimentalResourceWithRawResponse",
+ "ExperimentalResourceWithStreamingResponse",
+ "AsyncExperimentalResourceWithStreamingResponse",
+ "FileResource",
+ "AsyncFileResource",
+ "FileResourceWithRawResponse",
+ "AsyncFileResourceWithRawResponse",
+ "FileResourceWithStreamingResponse",
+ "AsyncFileResourceWithStreamingResponse",
+ "FormResource",
+ "AsyncFormResource",
+ "FormResourceWithRawResponse",
+ "AsyncFormResourceWithRawResponse",
+ "FormResourceWithStreamingResponse",
+ "AsyncFormResourceWithStreamingResponse",
+ "GenerateResource",
+ "AsyncGenerateResource",
+ "GenerateResourceWithRawResponse",
+ "AsyncGenerateResourceWithRawResponse",
+ "GenerateResourceWithStreamingResponse",
+ "AsyncGenerateResourceWithStreamingResponse",
+ "IntegrationResource",
+ "AsyncIntegrationResource",
+ "IntegrationResourceWithRawResponse",
+ "AsyncIntegrationResourceWithRawResponse",
+ "IntegrationResourceWithStreamingResponse",
+ "AsyncIntegrationResourceWithStreamingResponse",
+ "LocationResource",
+ "AsyncLocationResource",
+ "LocationResourceWithRawResponse",
+ "AsyncLocationResourceWithRawResponse",
+ "LocationResourceWithStreamingResponse",
+ "AsyncLocationResourceWithStreamingResponse",
+ "McpResource",
+ "AsyncMcpResource",
+ "McpResourceWithRawResponse",
+ "AsyncMcpResourceWithRawResponse",
+ "McpResourceWithStreamingResponse",
+ "AsyncMcpResourceWithStreamingResponse",
+ "MessageResource",
+ "AsyncMessageResource",
+ "MessageResourceWithRawResponse",
+ "AsyncMessageResourceWithRawResponse",
+ "MessageResourceWithStreamingResponse",
+ "AsyncMessageResourceWithStreamingResponse",
+ "MigrationResource",
+ "AsyncMigrationResource",
+ "MigrationResourceWithRawResponse",
+ "AsyncMigrationResourceWithRawResponse",
+ "MigrationResourceWithStreamingResponse",
+ "AsyncMigrationResourceWithStreamingResponse",
+ "ModelResource",
+ "AsyncModelResource",
+ "ModelResourceWithRawResponse",
+ "AsyncModelResourceWithRawResponse",
+ "ModelResourceWithStreamingResponse",
+ "AsyncModelResourceWithStreamingResponse",
+ "PermissionResource",
+ "AsyncPermissionResource",
+ "PermissionResourceWithRawResponse",
+ "AsyncPermissionResourceWithRawResponse",
+ "PermissionResourceWithStreamingResponse",
+ "AsyncPermissionResourceWithStreamingResponse",
+ "PluginResource",
+ "AsyncPluginResource",
+ "PluginResourceWithRawResponse",
+ "AsyncPluginResourceWithRawResponse",
+ "PluginResourceWithStreamingResponse",
+ "AsyncPluginResourceWithStreamingResponse",
+ "ProjectResource",
+ "AsyncProjectResource",
+ "ProjectResourceWithRawResponse",
+ "AsyncProjectResourceWithRawResponse",
+ "ProjectResourceWithStreamingResponse",
+ "AsyncProjectResourceWithStreamingResponse",
+ "ProviderResource",
+ "AsyncProviderResource",
+ "ProviderResourceWithRawResponse",
+ "AsyncProviderResourceWithRawResponse",
+ "ProviderResourceWithStreamingResponse",
+ "AsyncProviderResourceWithStreamingResponse",
+ "PtyResource",
+ "AsyncPtyResource",
+ "PtyResourceWithRawResponse",
+ "AsyncPtyResourceWithRawResponse",
+ "PtyResourceWithStreamingResponse",
+ "AsyncPtyResourceWithStreamingResponse",
+ "ReferenceResource",
+ "AsyncReferenceResource",
+ "ReferenceResourceWithRawResponse",
+ "AsyncReferenceResourceWithRawResponse",
+ "ReferenceResourceWithStreamingResponse",
+ "AsyncReferenceResourceWithStreamingResponse",
+ "RpcResource",
+ "AsyncRpcResource",
+ "RpcResourceWithRawResponse",
+ "AsyncRpcResourceWithRawResponse",
+ "RpcResourceWithStreamingResponse",
+ "AsyncRpcResourceWithStreamingResponse",
+ "ServerResource",
+ "AsyncServerResource",
+ "ServerResourceWithRawResponse",
+ "AsyncServerResourceWithRawResponse",
+ "ServerResourceWithStreamingResponse",
+ "AsyncServerResourceWithStreamingResponse",
+ "SessionResource",
+ "AsyncSessionResource",
+ "SessionResourceWithRawResponse",
+ "AsyncSessionResourceWithRawResponse",
+ "SessionResourceWithStreamingResponse",
+ "AsyncSessionResourceWithStreamingResponse",
+ "ShellResource",
+ "AsyncShellResource",
+ "ShellResourceWithRawResponse",
+ "AsyncShellResourceWithRawResponse",
+ "ShellResourceWithStreamingResponse",
+ "AsyncShellResourceWithStreamingResponse",
+ "SkillResource",
+ "AsyncSkillResource",
+ "SkillResourceWithRawResponse",
+ "AsyncSkillResourceWithRawResponse",
+ "SkillResourceWithStreamingResponse",
+ "AsyncSkillResourceWithStreamingResponse",
+ "VcsResource",
+ "AsyncVcsResource",
+ "VcsResourceWithRawResponse",
+ "AsyncVcsResourceWithRawResponse",
+ "VcsResourceWithStreamingResponse",
+ "AsyncVcsResourceWithStreamingResponse",
+ "VoiceResource",
+ "AsyncVoiceResource",
+ "VoiceResourceWithRawResponse",
+ "AsyncVoiceResourceWithRawResponse",
+ "VoiceResourceWithStreamingResponse",
+ "AsyncVoiceResourceWithStreamingResponse",
+ "WebsearchResource",
+ "AsyncWebsearchResource",
+ "WebsearchResourceWithRawResponse",
+ "AsyncWebsearchResourceWithRawResponse",
+ "WebsearchResourceWithStreamingResponse",
+ "AsyncWebsearchResourceWithStreamingResponse",
+ "WorktreeResource",
+ "AsyncWorktreeResource",
+ "WorktreeResourceWithRawResponse",
+ "AsyncWorktreeResourceWithRawResponse",
+ "WorktreeResourceWithStreamingResponse",
+ "AsyncWorktreeResourceWithStreamingResponse",
+]
diff --git a/src/opencode/resources/agent.py b/src/opencode/resources/agent.py
index 65193de..ebaa0b4 100644
--- a/src/opencode/resources/agent.py
+++ b/src/opencode/resources/agent.py
@@ -1,242 +1,261 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``agent`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
+from typing import Optional
+
+import httpx
-from .._base_client import RequestOptions
-from .._qs import encode_path_segment
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import agent_get_params, agent_list_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import path_template, maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- AgentGetResponse,
- AgentID,
- AgentListResponse,
- Location,
- )
+from .._base_client import make_request_options
+from ..types.shared.agent_id import AgentID
+from ..types.agent_get_response import AgentGetResponse
+from ..types.agent_list_response import AgentListResponse
__all__ = ["AgentResource", "AsyncAgentResource"]
class AgentResource(SyncAPIResource):
- """Operations under ``client.agent`` (OpenCode ``agent``)."""
-
@cached_property
def with_raw_response(self) -> AgentResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AgentResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AgentResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AgentResourceWithStreamingResponse(self)
def list(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[agent_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AgentListResponse:
- """List agents.
-
+ """
Retrieve currently registered agents.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/agent``
- """
- return cast(
- "AgentListResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/agent",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="AgentListResponse",
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/agent",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, agent_list_params.AgentListParams),
),
+ cast_to=AgentListResponse,
)
def get(
self,
agent_id: AgentID,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[agent_get_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AgentGetResponse:
- """Get agent.
-
+ """
Retrieve a single currently registered agent.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- AgentNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/agent/:agentID``
- """
- return cast(
- "AgentGetResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/agent/{encode_path_segment(agent_id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="AgentGetResponse",
- ),
- )
+ Args:
+ extra_headers: Send extra headers
+ extra_query: Add additional query parameters to the request
-class AgentResourceWithRawResponse:
- """``client.agent.with_raw_response``."""
-
- def __init__(self, resource: AgentResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
- self.get = to_raw_response_wrapper(resource.get)
+ extra_body: Add additional JSON properties to the request
-
-class AgentResourceWithStreamingResponse:
- """``client.agent.with_streaming_response``."""
-
- def __init__(self, resource: AgentResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
- self.get = to_streamed_response_wrapper(resource.get)
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not agent_id:
+ raise ValueError(f"Expected a non-empty value for `agent_id` but received {agent_id!r}")
+ return self._get(
+ path_template("/api/agent/{agent_id}", agent_id=agent_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, agent_get_params.AgentGetParams),
+ ),
+ cast_to=AgentGetResponse,
+ )
class AsyncAgentResource(AsyncAPIResource):
- """Operations under ``client.agent`` (OpenCode ``agent``)."""
-
@cached_property
def with_raw_response(self) -> AsyncAgentResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncAgentResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncAgentResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncAgentResourceWithStreamingResponse(self)
async def list(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[agent_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AgentListResponse:
- """List agents.
-
+ """
Retrieve currently registered agents.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/agent``
- """
- return cast(
- "AgentListResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/agent",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="AgentListResponse",
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/agent",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, agent_list_params.AgentListParams),
),
+ cast_to=AgentListResponse,
)
async def get(
self,
agent_id: AgentID,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[agent_get_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AgentGetResponse:
- """Get agent.
-
+ """
Retrieve a single currently registered agent.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- AgentNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/agent/:agentID``
- """
- return cast(
- "AgentGetResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/agent/{encode_path_segment(agent_id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="AgentGetResponse",
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not agent_id:
+ raise ValueError(f"Expected a non-empty value for `agent_id` but received {agent_id!r}")
+ return await self._get(
+ path_template("/api/agent/{agent_id}", agent_id=agent_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, agent_get_params.AgentGetParams),
),
+ cast_to=AgentGetResponse,
+ )
+
+
+class AgentResourceWithRawResponse:
+ def __init__(self, agent: AgentResource) -> None:
+ self._agent = agent
+
+ self.list = to_raw_response_wrapper(
+ agent.list,
+ )
+ self.get = to_raw_response_wrapper(
+ agent.get,
)
class AsyncAgentResourceWithRawResponse:
- """``client.agent.with_raw_response``."""
+ def __init__(self, agent: AsyncAgentResource) -> None:
+ self._agent = agent
+
+ self.list = async_to_raw_response_wrapper(
+ agent.list,
+ )
+ self.get = async_to_raw_response_wrapper(
+ agent.get,
+ )
+
+
+class AgentResourceWithStreamingResponse:
+ def __init__(self, agent: AgentResource) -> None:
+ self._agent = agent
- def __init__(self, resource: AsyncAgentResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
- self.get = async_to_raw_response_wrapper(resource.get)
+ self.list = to_streamed_response_wrapper(
+ agent.list,
+ )
+ self.get = to_streamed_response_wrapper(
+ agent.get,
+ )
class AsyncAgentResourceWithStreamingResponse:
- """``client.agent.with_streaming_response``."""
+ def __init__(self, agent: AsyncAgentResource) -> None:
+ self._agent = agent
- def __init__(self, resource: AsyncAgentResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.get = async_to_streamed_response_wrapper(resource.get)
+ self.list = async_to_streamed_response_wrapper(
+ agent.list,
+ )
+ self.get = async_to_streamed_response_wrapper(
+ agent.get,
+ )
diff --git a/src/opencode/resources/command.py b/src/opencode/resources/command.py
index 720ab18..ee1f706 100644
--- a/src/opencode/resources/command.py
+++ b/src/opencode/resources/command.py
@@ -1,159 +1,171 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``command`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
+from typing import Optional
+
+import httpx
-from .._base_client import RequestOptions
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import command_list_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- CommandListResponse,
- Location,
- )
+from .._base_client import make_request_options
+from ..types.command_list_response import CommandListResponse
__all__ = ["CommandResource", "AsyncCommandResource"]
class CommandResource(SyncAPIResource):
- """Operations under ``client.command`` (OpenCode ``command``)."""
-
@cached_property
def with_raw_response(self) -> CommandResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return CommandResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> CommandResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return CommandResourceWithStreamingResponse(self)
def list(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[command_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CommandListResponse:
- """List commands.
-
+ """
Retrieve currently registered commands.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ Args:
+ extra_headers: Send extra headers
- ``GET /api/command``
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "CommandListResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/command",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="CommandListResponse",
+ return self._get(
+ "/api/command",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, command_list_params.CommandListParams),
),
+ cast_to=CommandListResponse,
)
-class CommandResourceWithRawResponse:
- """``client.command.with_raw_response``."""
-
- def __init__(self, resource: CommandResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
-
-
-class CommandResourceWithStreamingResponse:
- """``client.command.with_streaming_response``."""
-
- def __init__(self, resource: CommandResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
-
-
class AsyncCommandResource(AsyncAPIResource):
- """Operations under ``client.command`` (OpenCode ``command``)."""
-
@cached_property
def with_raw_response(self) -> AsyncCommandResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncCommandResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncCommandResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncCommandResourceWithStreamingResponse(self)
async def list(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[command_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CommandListResponse:
- """List commands.
-
+ """
Retrieve currently registered commands.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
- ``GET /api/command``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "CommandListResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/command",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="CommandListResponse",
+ return await self._get(
+ "/api/command",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, command_list_params.CommandListParams),
),
+ cast_to=CommandListResponse,
+ )
+
+
+class CommandResourceWithRawResponse:
+ def __init__(self, command: CommandResource) -> None:
+ self._command = command
+
+ self.list = to_raw_response_wrapper(
+ command.list,
)
class AsyncCommandResourceWithRawResponse:
- """``client.command.with_raw_response``."""
+ def __init__(self, command: AsyncCommandResource) -> None:
+ self._command = command
+
+ self.list = async_to_raw_response_wrapper(
+ command.list,
+ )
+
+
+class CommandResourceWithStreamingResponse:
+ def __init__(self, command: CommandResource) -> None:
+ self._command = command
- def __init__(self, resource: AsyncCommandResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
+ self.list = to_streamed_response_wrapper(
+ command.list,
+ )
class AsyncCommandResourceWithStreamingResponse:
- """``client.command.with_streaming_response``."""
+ def __init__(self, command: AsyncCommandResource) -> None:
+ self._command = command
- def __init__(self, resource: AsyncCommandResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
+ self.list = async_to_streamed_response_wrapper(
+ command.list,
+ )
diff --git a/src/opencode/resources/config.py b/src/opencode/resources/config.py
index ecc24d3..6fd7b1b 100644
--- a/src/opencode/resources/config.py
+++ b/src/opencode/resources/config.py
@@ -1,313 +1,302 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``config`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-import builtins
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
+from typing import Optional
+
+import httpx
-from .._base_client import RequestOptions
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import config_get_params, config_update_params
+from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- ConfigEntry,
- ConfigShellOption,
- Location,
- )
+from .._base_client import make_request_options
+from ..types.config_get_response import ConfigGetResponse
+from ..types.config_shells_response import ConfigShellsResponse
__all__ = ["ConfigResource", "AsyncConfigResource"]
class ConfigResource(SyncAPIResource):
- """Operations under ``client.config`` (OpenCode ``config``)."""
-
@cached_property
def with_raw_response(self) -> ConfigResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return ConfigResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> ConfigResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return ConfigResourceWithStreamingResponse(self)
- def get(
+ def update(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ shell: Optional[str],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[ConfigEntry]:
- """Get configuration.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Patch supported fields in the highest-precedence global configuration document.
- Return configuration documents and discovery sources for the requested location, from lowest to highest priority.
+ Args:
+ extra_headers: Send extra headers
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ extra_query: Add additional query parameters to the request
- ``GET /api/config``
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "builtins.list[ConfigEntry]",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/config",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[ConfigEntry]",
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._patch(
+ "/api/experimental/config",
+ body=maybe_transform({"shell": shell}, config_update_params.ConfigUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=NoneType,
)
- def shells(
+ def get(
self,
*,
+ location: Optional[config_get_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[ConfigShellOption]:
- """List available shells.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ConfigGetResponse:
+ """
+ Return configuration documents and discovery sources for the requested location,
+ from lowest to highest priority.
+
+ Args:
+ extra_headers: Send extra headers
- Return shells available to terminal and agent execution.
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ extra_body: Add additional JSON properties to the request
- ``GET /api/config/shell``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "builtins.list[ConfigShellOption]",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/config/shell",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[ConfigShellOption]",
+ return self._get(
+ "/api/config",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, config_get_params.ConfigGetParams),
),
+ cast_to=ConfigGetResponse,
)
- def update(
+ def shells(
self,
*,
- shell: str | None,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Update global configuration.
-
- Patch supported fields in the highest-precedence global configuration document.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``PATCH /api/experimental/config``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="PATCH",
- path="/api/experimental/config",
- body={"shell": shell},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ConfigShellsResponse:
+ """Return shells available to terminal and agent execution."""
+ return self._get(
+ "/api/config/shell",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=ConfigShellsResponse,
)
-class ConfigResourceWithRawResponse:
- """``client.config.with_raw_response``."""
-
- def __init__(self, resource: ConfigResource) -> None:
- self._resource = resource
- self.get = to_raw_response_wrapper(resource.get)
- self.shells = to_raw_response_wrapper(resource.shells)
- self.update = to_raw_response_wrapper(resource.update)
-
-
-class ConfigResourceWithStreamingResponse:
- """``client.config.with_streaming_response``."""
-
- def __init__(self, resource: ConfigResource) -> None:
- self._resource = resource
- self.get = to_streamed_response_wrapper(resource.get)
- self.shells = to_streamed_response_wrapper(resource.shells)
- self.update = to_streamed_response_wrapper(resource.update)
-
-
class AsyncConfigResource(AsyncAPIResource):
- """Operations under ``client.config`` (OpenCode ``config``)."""
-
@cached_property
def with_raw_response(self) -> AsyncConfigResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncConfigResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncConfigResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncConfigResourceWithStreamingResponse(self)
- async def get(
+ async def update(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ shell: Optional[str],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[ConfigEntry]:
- """Get configuration.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Patch supported fields in the highest-precedence global configuration document.
- Return configuration documents and discovery sources for the requested location, from lowest to highest priority.
+ Args:
+ extra_headers: Send extra headers
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ extra_query: Add additional query parameters to the request
- ``GET /api/config``
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "builtins.list[ConfigEntry]",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/config",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[ConfigEntry]",
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._patch(
+ "/api/experimental/config",
+ body=await async_maybe_transform({"shell": shell}, config_update_params.ConfigUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=NoneType,
)
- async def shells(
+ async def get(
self,
*,
+ location: Optional[config_get_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[ConfigShellOption]:
- """List available shells.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ConfigGetResponse:
+ """
+ Return configuration documents and discovery sources for the requested location,
+ from lowest to highest priority.
+
+ Args:
+ extra_headers: Send extra headers
- Return shells available to terminal and agent execution.
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ extra_body: Add additional JSON properties to the request
- ``GET /api/config/shell``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "builtins.list[ConfigShellOption]",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/config/shell",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[ConfigShellOption]",
+ return await self._get(
+ "/api/config",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, config_get_params.ConfigGetParams),
),
+ cast_to=ConfigGetResponse,
)
- async def update(
+ async def shells(
self,
*,
- shell: str | None,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Update global configuration.
-
- Patch supported fields in the highest-precedence global configuration document.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ConfigShellsResponse:
+ """Return shells available to terminal and agent execution."""
+ return await self._get(
+ "/api/config/shell",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ConfigShellsResponse,
+ )
- This endpoint is experimental in OpenCode and may change without notice.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+class ConfigResourceWithRawResponse:
+ def __init__(self, config: ConfigResource) -> None:
+ self._config = config
- ``PATCH /api/experimental/config``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="PATCH",
- path="/api/experimental/config",
- body={"shell": shell},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
+ self.update = to_raw_response_wrapper(
+ config.update,
+ )
+ self.get = to_raw_response_wrapper(
+ config.get,
+ )
+ self.shells = to_raw_response_wrapper(
+ config.shells,
)
class AsyncConfigResourceWithRawResponse:
- """``client.config.with_raw_response``."""
+ def __init__(self, config: AsyncConfigResource) -> None:
+ self._config = config
+
+ self.update = async_to_raw_response_wrapper(
+ config.update,
+ )
+ self.get = async_to_raw_response_wrapper(
+ config.get,
+ )
+ self.shells = async_to_raw_response_wrapper(
+ config.shells,
+ )
+
- def __init__(self, resource: AsyncConfigResource) -> None:
- self._resource = resource
- self.get = async_to_raw_response_wrapper(resource.get)
- self.shells = async_to_raw_response_wrapper(resource.shells)
- self.update = async_to_raw_response_wrapper(resource.update)
+class ConfigResourceWithStreamingResponse:
+ def __init__(self, config: ConfigResource) -> None:
+ self._config = config
+
+ self.update = to_streamed_response_wrapper(
+ config.update,
+ )
+ self.get = to_streamed_response_wrapper(
+ config.get,
+ )
+ self.shells = to_streamed_response_wrapper(
+ config.shells,
+ )
class AsyncConfigResourceWithStreamingResponse:
- """``client.config.with_streaming_response``."""
+ def __init__(self, config: AsyncConfigResource) -> None:
+ self._config = config
- def __init__(self, resource: AsyncConfigResource) -> None:
- self._resource = resource
- self.get = async_to_streamed_response_wrapper(resource.get)
- self.shells = async_to_streamed_response_wrapper(resource.shells)
- self.update = async_to_streamed_response_wrapper(resource.update)
+ self.update = async_to_streamed_response_wrapper(
+ config.update,
+ )
+ self.get = async_to_streamed_response_wrapper(
+ config.get,
+ )
+ self.shells = async_to_streamed_response_wrapper(
+ config.shells,
+ )
diff --git a/src/opencode/resources/credential.py b/src/opencode/resources/credential.py
index 75a4739..8f8c10f 100644
--- a/src/opencode/resources/credential.py
+++ b/src/opencode/resources/credential.py
@@ -1,128 +1,95 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``credential`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-import builtins
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
+import httpx
-from .._base_client import RequestOptions
-from .._qs import encode_path_segment
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import credential_create_params, credential_update_params
+from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from .._utils import path_template, maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- CredentialEntry,
- CredentialID,
- CredentialValue,
- IntegrationID,
- )
+from .._base_client import make_request_options
+from ..types.shared.credential_id import CredentialID
+from ..types.shared.integration_id import IntegrationID
+from ..types.shared.credential_value import CredentialValue
+from ..types.credential_list_response import CredentialListResponse
+from ..types.credential_create_response import CredentialCreateResponse
__all__ = ["CredentialResource", "AsyncCredentialResource"]
class CredentialResource(SyncAPIResource):
- """Operations under ``client.credential`` (OpenCode ``credential``)."""
-
@cached_property
def with_raw_response(self) -> CredentialResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return CredentialResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> CredentialResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return CredentialResourceWithStreamingResponse(self)
-
- def list(
- self,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[CredentialEntry]:
- """List credentials.
-
- List every stored integration credential, including its secret value.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
- ``GET /api/credential``
+ For more information, see https://www.github.com/#with_streaming_response
"""
- return cast(
- "builtins.list[CredentialEntry]",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/credential",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[CredentialEntry]",
- unwrap=True,
- ),
- )
+ return CredentialResourceWithStreamingResponse(self)
def create(
self,
*,
integration_id: IntegrationID,
value: CredentialValue,
- id: CredentialID | NotGiven = NOT_GIVEN,
- label: str | NotGiven = NOT_GIVEN,
- activate: bool | NotGiven = NOT_GIVEN,
+ id: CredentialID | Omit = omit,
+ activate: bool | Omit = omit,
+ label: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> CredentialEntry:
- """Create credential.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> CredentialCreateResponse:
+ """
+ Store an integration credential. It becomes the integration's active credential
+ unless activate is false and the integration already has one. Fails with a
+ conflict when the requested ID already exists.
+
+ Args:
+ extra_headers: Send extra headers
- Store an integration credential. It becomes the integration's active credential unless activate is false and the integration already has one. Fails with a conflict when the requested ID already exists.
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ConflictError: The server answered 409.
+ extra_body: Add additional JSON properties to the request
- ``POST /api/credential``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "CredentialEntry",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/credential",
- body={
- "id": id,
- "integrationID": integration_id,
- "label": label,
- "value": value,
- "activate": activate,
- },
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="CredentialEntry",
- unwrap=True,
+ return self._post(
+ "/api/credential",
+ body=maybe_transform(
+ {
+ "integration_id": integration_id,
+ "value": value,
+ "id": id,
+ "activate": activate,
+ "label": label,
+ },
+ credential_create_params.CredentialCreateParams,
),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CredentialCreateResponse,
)
def update(
@@ -130,222 +97,190 @@ def update(
credential_id: CredentialID,
*,
label: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Update credential.
-
+ """
Update a stored credential label.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
- ``PATCH /api/credential/:credentialID``
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="PATCH",
- path=f"/api/credential/{encode_path_segment(credential_id)}",
- body={"label": label},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
+ if not credential_id:
+ raise ValueError(f"Expected a non-empty value for `credential_id` but received {credential_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._patch(
+ path_template("/api/credential/{credential_id}", credential_id=credential_id),
+ body=maybe_transform({"label": label}, credential_update_params.CredentialUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def list(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> CredentialListResponse:
+ """List every stored integration credential, including its secret value."""
+ return self._get(
+ "/api/credential",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=CredentialListResponse,
)
def activate(
self,
credential_id: CredentialID,
*,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Activate credential.
-
+ """
Activate a stored integration credential.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
- ``POST /api/credential/:credentialID/activate``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/credential/{encode_path_segment(credential_id)}/activate",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
+ if not credential_id:
+ raise ValueError(f"Expected a non-empty value for `credential_id` but received {credential_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template("/api/credential/{credential_id}/activate", credential_id=credential_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=NoneType,
)
def remove(
self,
credential_id: CredentialID,
*,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Remove credential.
-
+ """
Remove a stored integration credential.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
- ``DELETE /api/credential/:credentialID``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/credential/{encode_path_segment(credential_id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
+ if not credential_id:
+ raise ValueError(f"Expected a non-empty value for `credential_id` but received {credential_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template("/api/credential/{credential_id}", credential_id=credential_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=NoneType,
)
-class CredentialResourceWithRawResponse:
- """``client.credential.with_raw_response``."""
-
- def __init__(self, resource: CredentialResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
- self.create = to_raw_response_wrapper(resource.create)
- self.update = to_raw_response_wrapper(resource.update)
- self.activate = to_raw_response_wrapper(resource.activate)
- self.remove = to_raw_response_wrapper(resource.remove)
-
-
-class CredentialResourceWithStreamingResponse:
- """``client.credential.with_streaming_response``."""
-
- def __init__(self, resource: CredentialResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
- self.create = to_streamed_response_wrapper(resource.create)
- self.update = to_streamed_response_wrapper(resource.update)
- self.activate = to_streamed_response_wrapper(resource.activate)
- self.remove = to_streamed_response_wrapper(resource.remove)
-
-
class AsyncCredentialResource(AsyncAPIResource):
- """Operations under ``client.credential`` (OpenCode ``credential``)."""
-
@cached_property
def with_raw_response(self) -> AsyncCredentialResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncCredentialResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncCredentialResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncCredentialResourceWithStreamingResponse(self)
-
- async def list(
- self,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[CredentialEntry]:
- """List credentials.
-
- List every stored integration credential, including its secret value.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
- ``GET /api/credential``
+ For more information, see https://www.github.com/#with_streaming_response
"""
- return cast(
- "builtins.list[CredentialEntry]",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/credential",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[CredentialEntry]",
- unwrap=True,
- ),
- )
+ return AsyncCredentialResourceWithStreamingResponse(self)
async def create(
self,
*,
integration_id: IntegrationID,
value: CredentialValue,
- id: CredentialID | NotGiven = NOT_GIVEN,
- label: str | NotGiven = NOT_GIVEN,
- activate: bool | NotGiven = NOT_GIVEN,
+ id: CredentialID | Omit = omit,
+ activate: bool | Omit = omit,
+ label: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> CredentialEntry:
- """Create credential.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> CredentialCreateResponse:
+ """
+ Store an integration credential. It becomes the integration's active credential
+ unless activate is false and the integration already has one. Fails with a
+ conflict when the requested ID already exists.
+
+ Args:
+ extra_headers: Send extra headers
- Store an integration credential. It becomes the integration's active credential unless activate is false and the integration already has one. Fails with a conflict when the requested ID already exists.
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ConflictError: The server answered 409.
+ extra_body: Add additional JSON properties to the request
- ``POST /api/credential``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "CredentialEntry",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/credential",
- body={
- "id": id,
- "integrationID": integration_id,
- "label": label,
- "value": value,
- "activate": activate,
- },
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="CredentialEntry",
- unwrap=True,
+ return await self._post(
+ "/api/credential",
+ body=await async_maybe_transform(
+ {
+ "integration_id": integration_id,
+ "value": value,
+ "id": id,
+ "activate": activate,
+ "label": label,
+ },
+ credential_create_params.CredentialCreateParams,
),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=CredentialCreateResponse,
)
async def update(
@@ -353,125 +288,204 @@ async def update(
credential_id: CredentialID,
*,
label: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Update credential.
-
+ """
Update a stored credential label.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
- ``PATCH /api/credential/:credentialID``
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="PATCH",
- path=f"/api/credential/{encode_path_segment(credential_id)}",
- body={"label": label},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
+ if not credential_id:
+ raise ValueError(f"Expected a non-empty value for `credential_id` but received {credential_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._patch(
+ path_template("/api/credential/{credential_id}", credential_id=credential_id),
+ body=await async_maybe_transform({"label": label}, credential_update_params.CredentialUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def list(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> CredentialListResponse:
+ """List every stored integration credential, including its secret value."""
+ return await self._get(
+ "/api/credential",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=CredentialListResponse,
)
async def activate(
self,
credential_id: CredentialID,
*,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Activate credential.
-
+ """
Activate a stored integration credential.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
+ Args:
+ extra_headers: Send extra headers
- ``POST /api/credential/:credentialID/activate``
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/credential/{encode_path_segment(credential_id)}/activate",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
+ if not credential_id:
+ raise ValueError(f"Expected a non-empty value for `credential_id` but received {credential_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template("/api/credential/{credential_id}/activate", credential_id=credential_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=NoneType,
)
async def remove(
self,
credential_id: CredentialID,
*,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Remove credential.
-
+ """
Remove a stored integration credential.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
+ Args:
+ extra_headers: Send extra headers
- ``DELETE /api/credential/:credentialID``
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/credential/{encode_path_segment(credential_id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
+ if not credential_id:
+ raise ValueError(f"Expected a non-empty value for `credential_id` but received {credential_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template("/api/credential/{credential_id}", credential_id=credential_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=NoneType,
+ )
+
+
+class CredentialResourceWithRawResponse:
+ def __init__(self, credential: CredentialResource) -> None:
+ self._credential = credential
+
+ self.create = to_raw_response_wrapper(
+ credential.create,
+ )
+ self.update = to_raw_response_wrapper(
+ credential.update,
+ )
+ self.list = to_raw_response_wrapper(
+ credential.list,
+ )
+ self.activate = to_raw_response_wrapper(
+ credential.activate,
+ )
+ self.remove = to_raw_response_wrapper(
+ credential.remove,
)
class AsyncCredentialResourceWithRawResponse:
- """``client.credential.with_raw_response``."""
+ def __init__(self, credential: AsyncCredentialResource) -> None:
+ self._credential = credential
- def __init__(self, resource: AsyncCredentialResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
- self.create = async_to_raw_response_wrapper(resource.create)
- self.update = async_to_raw_response_wrapper(resource.update)
- self.activate = async_to_raw_response_wrapper(resource.activate)
- self.remove = async_to_raw_response_wrapper(resource.remove)
+ self.create = async_to_raw_response_wrapper(
+ credential.create,
+ )
+ self.update = async_to_raw_response_wrapper(
+ credential.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ credential.list,
+ )
+ self.activate = async_to_raw_response_wrapper(
+ credential.activate,
+ )
+ self.remove = async_to_raw_response_wrapper(
+ credential.remove,
+ )
+
+
+class CredentialResourceWithStreamingResponse:
+ def __init__(self, credential: CredentialResource) -> None:
+ self._credential = credential
+
+ self.create = to_streamed_response_wrapper(
+ credential.create,
+ )
+ self.update = to_streamed_response_wrapper(
+ credential.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ credential.list,
+ )
+ self.activate = to_streamed_response_wrapper(
+ credential.activate,
+ )
+ self.remove = to_streamed_response_wrapper(
+ credential.remove,
+ )
class AsyncCredentialResourceWithStreamingResponse:
- """``client.credential.with_streaming_response``."""
-
- def __init__(self, resource: AsyncCredentialResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.create = async_to_streamed_response_wrapper(resource.create)
- self.update = async_to_streamed_response_wrapper(resource.update)
- self.activate = async_to_streamed_response_wrapper(resource.activate)
- self.remove = async_to_streamed_response_wrapper(resource.remove)
+ def __init__(self, credential: AsyncCredentialResource) -> None:
+ self._credential = credential
+
+ self.create = async_to_streamed_response_wrapper(
+ credential.create,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ credential.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ credential.list,
+ )
+ self.activate = async_to_streamed_response_wrapper(
+ credential.activate,
+ )
+ self.remove = async_to_streamed_response_wrapper(
+ credential.remove,
+ )
diff --git a/src/opencode/resources/debug/__init__.py b/src/opencode/resources/debug/__init__.py
new file mode 100644
index 0000000..efe8664
--- /dev/null
+++ b/src/opencode/resources/debug/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from .debug import (
+ DebugResource,
+ AsyncDebugResource,
+ DebugResourceWithRawResponse,
+ AsyncDebugResourceWithRawResponse,
+ DebugResourceWithStreamingResponse,
+ AsyncDebugResourceWithStreamingResponse,
+)
+from .location import (
+ LocationResource,
+ AsyncLocationResource,
+ LocationResourceWithRawResponse,
+ AsyncLocationResourceWithRawResponse,
+ LocationResourceWithStreamingResponse,
+ AsyncLocationResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "LocationResource",
+ "AsyncLocationResource",
+ "LocationResourceWithRawResponse",
+ "AsyncLocationResourceWithRawResponse",
+ "LocationResourceWithStreamingResponse",
+ "AsyncLocationResourceWithStreamingResponse",
+ "DebugResource",
+ "AsyncDebugResource",
+ "DebugResourceWithRawResponse",
+ "AsyncDebugResourceWithRawResponse",
+ "DebugResourceWithStreamingResponse",
+ "AsyncDebugResourceWithStreamingResponse",
+]
diff --git a/src/opencode/resources/debug/debug.py b/src/opencode/resources/debug/debug.py
new file mode 100644
index 0000000..5e4a56d
--- /dev/null
+++ b/src/opencode/resources/debug/debug.py
@@ -0,0 +1,105 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import Body, Query, Headers, NotGiven, not_given
+from .location import (
+ LocationResource,
+ AsyncLocationResource,
+ LocationResourceWithRawResponse,
+ AsyncLocationResourceWithRawResponse,
+ LocationResourceWithStreamingResponse,
+ AsyncLocationResourceWithStreamingResponse,
+)
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+
+__all__ = ["DebugResource", "AsyncDebugResource"]
+
+
+class DebugResource(SyncAPIResource):
+ @cached_property
+ def location(self) -> LocationResource:
+ return LocationResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> DebugResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return DebugResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> DebugResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return DebugResourceWithStreamingResponse(self)
+
+
+class AsyncDebugResource(AsyncAPIResource):
+ @cached_property
+ def location(self) -> AsyncLocationResource:
+ return AsyncLocationResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncDebugResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncDebugResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncDebugResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncDebugResourceWithStreamingResponse(self)
+
+
+class DebugResourceWithRawResponse:
+ def __init__(self, debug: DebugResource) -> None:
+ self._debug = debug
+
+ @cached_property
+ def location(self) -> LocationResourceWithRawResponse:
+ return LocationResourceWithRawResponse(self._debug.location)
+
+
+class AsyncDebugResourceWithRawResponse:
+ def __init__(self, debug: AsyncDebugResource) -> None:
+ self._debug = debug
+
+ @cached_property
+ def location(self) -> AsyncLocationResourceWithRawResponse:
+ return AsyncLocationResourceWithRawResponse(self._debug.location)
+
+
+class DebugResourceWithStreamingResponse:
+ def __init__(self, debug: DebugResource) -> None:
+ self._debug = debug
+
+ @cached_property
+ def location(self) -> LocationResourceWithStreamingResponse:
+ return LocationResourceWithStreamingResponse(self._debug.location)
+
+
+class AsyncDebugResourceWithStreamingResponse:
+ def __init__(self, debug: AsyncDebugResource) -> None:
+ self._debug = debug
+
+ @cached_property
+ def location(self) -> AsyncLocationResourceWithStreamingResponse:
+ return AsyncLocationResourceWithStreamingResponse(self._debug.location)
diff --git a/src/opencode/resources/debug/location.py b/src/opencode/resources/debug/location.py
new file mode 100644
index 0000000..ca5bcfa
--- /dev/null
+++ b/src/opencode/resources/debug/location.py
@@ -0,0 +1,225 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...types.debug import location_evict_params
+from ..._base_client import make_request_options
+from ...types.debug.location_list_response import LocationListResponse
+
+__all__ = ["LocationResource", "AsyncLocationResource"]
+
+
+class LocationResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> LocationResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return LocationResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> LocationResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return LocationResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> LocationListResponse:
+ """List locations currently loaded by the server."""
+ return self._get(
+ "/api/debug/location",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LocationListResponse,
+ )
+
+ def evict(
+ self,
+ *,
+ location: Optional[location_evict_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Dispose the requested location's cached services so its next use boots them
+ fresh.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ "/api/debug/location",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, location_evict_params.LocationEvictParams),
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncLocationResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncLocationResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncLocationResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncLocationResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncLocationResourceWithStreamingResponse(self)
+
+ async def list(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> LocationListResponse:
+ """List locations currently loaded by the server."""
+ return await self._get(
+ "/api/debug/location",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LocationListResponse,
+ )
+
+ async def evict(
+ self,
+ *,
+ location: Optional[location_evict_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Dispose the requested location's cached services so its next use boots them
+ fresh.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ "/api/debug/location",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, location_evict_params.LocationEvictParams),
+ ),
+ cast_to=NoneType,
+ )
+
+
+class LocationResourceWithRawResponse:
+ def __init__(self, location: LocationResource) -> None:
+ self._location = location
+
+ self.list = to_raw_response_wrapper(
+ location.list,
+ )
+ self.evict = to_raw_response_wrapper(
+ location.evict,
+ )
+
+
+class AsyncLocationResourceWithRawResponse:
+ def __init__(self, location: AsyncLocationResource) -> None:
+ self._location = location
+
+ self.list = async_to_raw_response_wrapper(
+ location.list,
+ )
+ self.evict = async_to_raw_response_wrapper(
+ location.evict,
+ )
+
+
+class LocationResourceWithStreamingResponse:
+ def __init__(self, location: LocationResource) -> None:
+ self._location = location
+
+ self.list = to_streamed_response_wrapper(
+ location.list,
+ )
+ self.evict = to_streamed_response_wrapper(
+ location.evict,
+ )
+
+
+class AsyncLocationResourceWithStreamingResponse:
+ def __init__(self, location: AsyncLocationResource) -> None:
+ self._location = location
+
+ self.list = async_to_streamed_response_wrapper(
+ location.list,
+ )
+ self.evict = async_to_streamed_response_wrapper(
+ location.evict,
+ )
diff --git a/src/opencode/resources/event.py b/src/opencode/resources/event.py
index c6583f9..3f49558 100644
--- a/src/opencode/resources/event.py
+++ b/src/opencode/resources/event.py
@@ -1,147 +1,148 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``event`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from functools import cached_property
-from typing import TYPE_CHECKING
+import httpx
-from .._base_client import RequestOptions
-from .._resource import AsyncAPIResource, SyncAPIResource
+from .._types import Body, Query, Headers, NotGiven, not_given
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._streaming import AsyncStream, Stream
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- Event,
- )
+from .._streaming import Stream, AsyncStream
+from .._base_client import make_request_options
+from ..types.shared.event import Event
__all__ = ["EventResource", "AsyncEventResource"]
class EventResource(SyncAPIResource):
- """Operations under ``client.event`` (OpenCode ``event``)."""
-
@cached_property
def with_raw_response(self) -> EventResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return EventResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> EventResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return EventResourceWithStreamingResponse(self)
def subscribe(
self,
*,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Stream[Event]:
- """Subscribe to events.
-
- Subscribe to native events and plugin RPC events across all server locations. Volatile by contract: a slow consumer overflows and fails the stream, and events during disconnection are missed.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
-
- ``GET /api/event``
"""
- return self._client.stream(
- RequestOptions(
- method="GET",
- path="/api/event",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
+ Subscribe to native events and plugin RPC events across all server locations.
+ Volatile by contract: a slow consumer overflows and fails the stream, and events
+ during disconnection are missed.
+ """
+ return self._get(
+ "/api/event",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- item_type="Event",
+ cast_to=Event,
+ stream=True,
+ stream_cls=Stream[Event],
)
-class EventResourceWithRawResponse:
- """``client.event.with_raw_response``."""
-
- def __init__(self, resource: EventResource) -> None:
- self._resource = resource
- self.subscribe = to_raw_response_wrapper(resource.subscribe)
-
-
-class EventResourceWithStreamingResponse:
- """``client.event.with_streaming_response``."""
-
- def __init__(self, resource: EventResource) -> None:
- self._resource = resource
- self.subscribe = to_streamed_response_wrapper(resource.subscribe)
-
-
class AsyncEventResource(AsyncAPIResource):
- """Operations under ``client.event`` (OpenCode ``event``)."""
-
@cached_property
def with_raw_response(self) -> AsyncEventResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncEventResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncEventResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncEventResourceWithStreamingResponse(self)
async def subscribe(
self,
*,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AsyncStream[Event]:
- """Subscribe to events.
+ """
+ Subscribe to native events and plugin RPC events across all server locations.
+ Volatile by contract: a slow consumer overflows and fails the stream, and events
+ during disconnection are missed.
+ """
+ return await self._get(
+ "/api/event",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Event,
+ stream=True,
+ stream_cls=AsyncStream[Event],
+ )
- Subscribe to native events and plugin RPC events across all server locations. Volatile by contract: a slow consumer overflows and fails the stream, and events during disconnection are missed.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
+class EventResourceWithRawResponse:
+ def __init__(self, event: EventResource) -> None:
+ self._event = event
- ``GET /api/event``
- """
- return await self._client.stream(
- RequestOptions(
- method="GET",
- path="/api/event",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- item_type="Event",
+ self.subscribe = to_raw_response_wrapper(
+ event.subscribe,
)
class AsyncEventResourceWithRawResponse:
- """``client.event.with_raw_response``."""
+ def __init__(self, event: AsyncEventResource) -> None:
+ self._event = event
- def __init__(self, resource: AsyncEventResource) -> None:
- self._resource = resource
- self.subscribe = async_to_raw_response_wrapper(resource.subscribe)
+ self.subscribe = async_to_raw_response_wrapper(
+ event.subscribe,
+ )
+
+
+class EventResourceWithStreamingResponse:
+ def __init__(self, event: EventResource) -> None:
+ self._event = event
+
+ self.subscribe = to_streamed_response_wrapper(
+ event.subscribe,
+ )
class AsyncEventResourceWithStreamingResponse:
- """``client.event.with_streaming_response``."""
+ def __init__(self, event: AsyncEventResource) -> None:
+ self._event = event
- def __init__(self, resource: AsyncEventResource) -> None:
- self._resource = resource
- self.subscribe = async_to_streamed_response_wrapper(resource.subscribe)
+ self.subscribe = async_to_streamed_response_wrapper(
+ event.subscribe,
+ )
diff --git a/src/opencode/resources/experimental/__init__.py b/src/opencode/resources/experimental/__init__.py
new file mode 100644
index 0000000..2bd7417
--- /dev/null
+++ b/src/opencode/resources/experimental/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from .experimental import (
+ ExperimentalResource,
+ AsyncExperimentalResource,
+ ExperimentalResourceWithRawResponse,
+ AsyncExperimentalResourceWithRawResponse,
+ ExperimentalResourceWithStreamingResponse,
+ AsyncExperimentalResourceWithStreamingResponse,
+)
+from .persistent_pty import (
+ PersistentPtyResource,
+ AsyncPersistentPtyResource,
+ PersistentPtyResourceWithRawResponse,
+ AsyncPersistentPtyResourceWithRawResponse,
+ PersistentPtyResourceWithStreamingResponse,
+ AsyncPersistentPtyResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "PersistentPtyResource",
+ "AsyncPersistentPtyResource",
+ "PersistentPtyResourceWithRawResponse",
+ "AsyncPersistentPtyResourceWithRawResponse",
+ "PersistentPtyResourceWithStreamingResponse",
+ "AsyncPersistentPtyResourceWithStreamingResponse",
+ "ExperimentalResource",
+ "AsyncExperimentalResource",
+ "ExperimentalResourceWithRawResponse",
+ "AsyncExperimentalResourceWithRawResponse",
+ "ExperimentalResourceWithStreamingResponse",
+ "AsyncExperimentalResourceWithStreamingResponse",
+]
diff --git a/src/opencode/resources/experimental/experimental.py b/src/opencode/resources/experimental/experimental.py
new file mode 100644
index 0000000..0f58419
--- /dev/null
+++ b/src/opencode/resources/experimental/experimental.py
@@ -0,0 +1,105 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import Body, Query, Headers, NotGiven, not_given
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from .persistent_pty import (
+ PersistentPtyResource,
+ AsyncPersistentPtyResource,
+ PersistentPtyResourceWithRawResponse,
+ AsyncPersistentPtyResourceWithRawResponse,
+ PersistentPtyResourceWithStreamingResponse,
+ AsyncPersistentPtyResourceWithStreamingResponse,
+)
+
+__all__ = ["ExperimentalResource", "AsyncExperimentalResource"]
+
+
+class ExperimentalResource(SyncAPIResource):
+ @cached_property
+ def persistent_pty(self) -> PersistentPtyResource:
+ return PersistentPtyResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> ExperimentalResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return ExperimentalResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ExperimentalResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return ExperimentalResourceWithStreamingResponse(self)
+
+
+class AsyncExperimentalResource(AsyncAPIResource):
+ @cached_property
+ def persistent_pty(self) -> AsyncPersistentPtyResource:
+ return AsyncPersistentPtyResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncExperimentalResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncExperimentalResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncExperimentalResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncExperimentalResourceWithStreamingResponse(self)
+
+
+class ExperimentalResourceWithRawResponse:
+ def __init__(self, experimental: ExperimentalResource) -> None:
+ self._experimental = experimental
+
+ @cached_property
+ def persistent_pty(self) -> PersistentPtyResourceWithRawResponse:
+ return PersistentPtyResourceWithRawResponse(self._experimental.persistent_pty)
+
+
+class AsyncExperimentalResourceWithRawResponse:
+ def __init__(self, experimental: AsyncExperimentalResource) -> None:
+ self._experimental = experimental
+
+ @cached_property
+ def persistent_pty(self) -> AsyncPersistentPtyResourceWithRawResponse:
+ return AsyncPersistentPtyResourceWithRawResponse(self._experimental.persistent_pty)
+
+
+class ExperimentalResourceWithStreamingResponse:
+ def __init__(self, experimental: ExperimentalResource) -> None:
+ self._experimental = experimental
+
+ @cached_property
+ def persistent_pty(self) -> PersistentPtyResourceWithStreamingResponse:
+ return PersistentPtyResourceWithStreamingResponse(self._experimental.persistent_pty)
+
+
+class AsyncExperimentalResourceWithStreamingResponse:
+ def __init__(self, experimental: AsyncExperimentalResource) -> None:
+ self._experimental = experimental
+
+ @cached_property
+ def persistent_pty(self) -> AsyncPersistentPtyResourceWithStreamingResponse:
+ return AsyncPersistentPtyResourceWithStreamingResponse(self._experimental.persistent_pty)
diff --git a/src/opencode/resources/experimental/persistent_pty.py b/src/opencode/resources/experimental/persistent_pty.py
new file mode 100644
index 0000000..7482f95
--- /dev/null
+++ b/src/opencode/resources/experimental/persistent_pty.py
@@ -0,0 +1,881 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Optional
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given
+from ..._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.experimental import persistent_pty_read_params, persistent_pty_create_params, persistent_pty_update_params
+from ...types.shared.pty_id import PtyID
+from ...types.shared.session_id import SessionID
+from ...types.shared.persistent_pty_read_lines import PersistentPtyReadLines
+from ...types.experimental.persistent_pty_get_response import PersistentPtyGetResponse
+from ...types.experimental.persistent_pty_list_response import PersistentPtyListResponse
+from ...types.experimental.persistent_pty_read_response import PersistentPtyReadResponse
+from ...types.experimental.persistent_pty_create_response import PersistentPtyCreateResponse
+from ...types.experimental.persistent_pty_update_response import PersistentPtyUpdateResponse
+from ...types.experimental.persistent_pty_handoff_response import PersistentPtyHandoffResponse
+from ...types.experimental.persistent_pty_snapshot_response import PersistentPtySnapshotResponse
+from ...types.experimental.persistent_pty_connect_token_response import PersistentPtyConnectTokenResponse
+
+__all__ = ["PersistentPtyResource", "AsyncPersistentPtyResource"]
+
+
+class PersistentPtyResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> PersistentPtyResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return PersistentPtyResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> PersistentPtyResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return PersistentPtyResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ session_id: SessionID,
+ *,
+ args: SequenceNotStr[str],
+ env: Dict[str, str],
+ title: str,
+ command: str | Omit = omit,
+ cwd: str | Omit = omit,
+ size: persistent_pty_create_params.Size | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PersistentPtyCreateResponse:
+ """
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._post(
+ path_template("/api/experimental/session/{session_id}/terminal", session_id=session_id),
+ body=maybe_transform(
+ {
+ "args": args,
+ "env": env,
+ "title": title,
+ "command": command,
+ "cwd": cwd,
+ "size": size,
+ },
+ persistent_pty_create_params.PersistentPtyCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PersistentPtyCreateResponse,
+ )
+
+ def update(
+ self,
+ pty_id: PtyID,
+ *,
+ size: persistent_pty_update_params.Size,
+ attachment_id: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PersistentPtyUpdateResponse:
+ """
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not pty_id:
+ raise ValueError(f"Expected a non-empty value for `pty_id` but received {pty_id!r}")
+ return self._put(
+ path_template("/api/experimental/persistent-pty/{pty_id}", pty_id=pty_id),
+ body=maybe_transform(
+ {
+ "size": size,
+ "attachment_id": attachment_id,
+ },
+ persistent_pty_update_params.PersistentPtyUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PersistentPtyUpdateResponse,
+ )
+
+ def list(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PersistentPtyListResponse:
+ """
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._get(
+ path_template("/api/experimental/session/{session_id}/terminal", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PersistentPtyListResponse,
+ )
+
+ def connect_token(
+ self,
+ pty_id: PtyID,
+ *,
+ x_opencode_ticket: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PersistentPtyConnectTokenResponse:
+ """
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not pty_id:
+ raise ValueError(f"Expected a non-empty value for `pty_id` but received {pty_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {"x-opencode-ticket": str(x_opencode_ticket) if is_given(x_opencode_ticket) else not_given}
+ ),
+ **(extra_headers or {}),
+ }
+ return self._post(
+ path_template("/api/experimental/persistent-pty/{pty_id}/connect-token", pty_id=pty_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PersistentPtyConnectTokenResponse,
+ )
+
+ def get(
+ self,
+ pty_id: PtyID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PersistentPtyGetResponse:
+ """
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not pty_id:
+ raise ValueError(f"Expected a non-empty value for `pty_id` but received {pty_id!r}")
+ return self._get(
+ path_template("/api/experimental/persistent-pty/{pty_id}", pty_id=pty_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PersistentPtyGetResponse,
+ )
+
+ def handoff(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PersistentPtyHandoffResponse:
+ return self._post(
+ "/api/experimental/persistent-pty/handoff",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PersistentPtyHandoffResponse,
+ )
+
+ def read(
+ self,
+ session_id: SessionID,
+ *,
+ lines: Optional[PersistentPtyReadLines] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PersistentPtyReadResponse:
+ """
+ Read the last physical rows without changing selection or taking control.
+ Omitted lines uses the live terminal height; larger counts include retained
+ history. Blank rows are preserved. Screen dimensions and cursor remain relative
+ to the live screen. Returns null when no current terminal exists. Selection is
+ server-local and resets on restart. Experimental: may change without
+ compatibility guarantees.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._get(
+ path_template("/api/experimental/session/{session_id}/terminal/read", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"lines": lines}, persistent_pty_read_params.PersistentPtyReadParams),
+ ),
+ cast_to=PersistentPtyReadResponse,
+ )
+
+ def remove(
+ self,
+ pty_id: PtyID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not pty_id:
+ raise ValueError(f"Expected a non-empty value for `pty_id` but received {pty_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template("/api/experimental/persistent-pty/{pty_id}", pty_id=pty_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def shutdown(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ "/api/experimental/persistent-pty/shutdown",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def snapshot(
+ self,
+ pty_id: PtyID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PersistentPtySnapshotResponse:
+ """
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not pty_id:
+ raise ValueError(f"Expected a non-empty value for `pty_id` but received {pty_id!r}")
+ return self._get(
+ path_template("/api/experimental/persistent-pty/{pty_id}/snapshot", pty_id=pty_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PersistentPtySnapshotResponse,
+ )
+
+
+class AsyncPersistentPtyResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncPersistentPtyResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncPersistentPtyResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncPersistentPtyResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncPersistentPtyResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ session_id: SessionID,
+ *,
+ args: SequenceNotStr[str],
+ env: Dict[str, str],
+ title: str,
+ command: str | Omit = omit,
+ cwd: str | Omit = omit,
+ size: persistent_pty_create_params.Size | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PersistentPtyCreateResponse:
+ """
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._post(
+ path_template("/api/experimental/session/{session_id}/terminal", session_id=session_id),
+ body=await async_maybe_transform(
+ {
+ "args": args,
+ "env": env,
+ "title": title,
+ "command": command,
+ "cwd": cwd,
+ "size": size,
+ },
+ persistent_pty_create_params.PersistentPtyCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PersistentPtyCreateResponse,
+ )
+
+ async def update(
+ self,
+ pty_id: PtyID,
+ *,
+ size: persistent_pty_update_params.Size,
+ attachment_id: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PersistentPtyUpdateResponse:
+ """
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not pty_id:
+ raise ValueError(f"Expected a non-empty value for `pty_id` but received {pty_id!r}")
+ return await self._put(
+ path_template("/api/experimental/persistent-pty/{pty_id}", pty_id=pty_id),
+ body=await async_maybe_transform(
+ {
+ "size": size,
+ "attachment_id": attachment_id,
+ },
+ persistent_pty_update_params.PersistentPtyUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PersistentPtyUpdateResponse,
+ )
+
+ async def list(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PersistentPtyListResponse:
+ """
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._get(
+ path_template("/api/experimental/session/{session_id}/terminal", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PersistentPtyListResponse,
+ )
+
+ async def connect_token(
+ self,
+ pty_id: PtyID,
+ *,
+ x_opencode_ticket: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PersistentPtyConnectTokenResponse:
+ """
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not pty_id:
+ raise ValueError(f"Expected a non-empty value for `pty_id` but received {pty_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {"x-opencode-ticket": str(x_opencode_ticket) if is_given(x_opencode_ticket) else not_given}
+ ),
+ **(extra_headers or {}),
+ }
+ return await self._post(
+ path_template("/api/experimental/persistent-pty/{pty_id}/connect-token", pty_id=pty_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PersistentPtyConnectTokenResponse,
+ )
+
+ async def get(
+ self,
+ pty_id: PtyID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PersistentPtyGetResponse:
+ """
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not pty_id:
+ raise ValueError(f"Expected a non-empty value for `pty_id` but received {pty_id!r}")
+ return await self._get(
+ path_template("/api/experimental/persistent-pty/{pty_id}", pty_id=pty_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PersistentPtyGetResponse,
+ )
+
+ async def handoff(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PersistentPtyHandoffResponse:
+ return await self._post(
+ "/api/experimental/persistent-pty/handoff",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PersistentPtyHandoffResponse,
+ )
+
+ async def read(
+ self,
+ session_id: SessionID,
+ *,
+ lines: Optional[PersistentPtyReadLines] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PersistentPtyReadResponse:
+ """
+ Read the last physical rows without changing selection or taking control.
+ Omitted lines uses the live terminal height; larger counts include retained
+ history. Blank rows are preserved. Screen dimensions and cursor remain relative
+ to the live screen. Returns null when no current terminal exists. Selection is
+ server-local and resets on restart. Experimental: may change without
+ compatibility guarantees.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._get(
+ path_template("/api/experimental/session/{session_id}/terminal/read", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"lines": lines}, persistent_pty_read_params.PersistentPtyReadParams),
+ ),
+ cast_to=PersistentPtyReadResponse,
+ )
+
+ async def remove(
+ self,
+ pty_id: PtyID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not pty_id:
+ raise ValueError(f"Expected a non-empty value for `pty_id` but received {pty_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template("/api/experimental/persistent-pty/{pty_id}", pty_id=pty_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def shutdown(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ "/api/experimental/persistent-pty/shutdown",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def snapshot(
+ self,
+ pty_id: PtyID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PersistentPtySnapshotResponse:
+ """
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not pty_id:
+ raise ValueError(f"Expected a non-empty value for `pty_id` but received {pty_id!r}")
+ return await self._get(
+ path_template("/api/experimental/persistent-pty/{pty_id}/snapshot", pty_id=pty_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PersistentPtySnapshotResponse,
+ )
+
+
+class PersistentPtyResourceWithRawResponse:
+ def __init__(self, persistent_pty: PersistentPtyResource) -> None:
+ self._persistent_pty = persistent_pty
+
+ self.create = to_raw_response_wrapper(
+ persistent_pty.create,
+ )
+ self.update = to_raw_response_wrapper(
+ persistent_pty.update,
+ )
+ self.list = to_raw_response_wrapper(
+ persistent_pty.list,
+ )
+ self.connect_token = to_raw_response_wrapper(
+ persistent_pty.connect_token,
+ )
+ self.get = to_raw_response_wrapper(
+ persistent_pty.get,
+ )
+ self.handoff = to_raw_response_wrapper(
+ persistent_pty.handoff,
+ )
+ self.read = to_raw_response_wrapper(
+ persistent_pty.read,
+ )
+ self.remove = to_raw_response_wrapper(
+ persistent_pty.remove,
+ )
+ self.shutdown = to_raw_response_wrapper(
+ persistent_pty.shutdown,
+ )
+ self.snapshot = to_raw_response_wrapper(
+ persistent_pty.snapshot,
+ )
+
+
+class AsyncPersistentPtyResourceWithRawResponse:
+ def __init__(self, persistent_pty: AsyncPersistentPtyResource) -> None:
+ self._persistent_pty = persistent_pty
+
+ self.create = async_to_raw_response_wrapper(
+ persistent_pty.create,
+ )
+ self.update = async_to_raw_response_wrapper(
+ persistent_pty.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ persistent_pty.list,
+ )
+ self.connect_token = async_to_raw_response_wrapper(
+ persistent_pty.connect_token,
+ )
+ self.get = async_to_raw_response_wrapper(
+ persistent_pty.get,
+ )
+ self.handoff = async_to_raw_response_wrapper(
+ persistent_pty.handoff,
+ )
+ self.read = async_to_raw_response_wrapper(
+ persistent_pty.read,
+ )
+ self.remove = async_to_raw_response_wrapper(
+ persistent_pty.remove,
+ )
+ self.shutdown = async_to_raw_response_wrapper(
+ persistent_pty.shutdown,
+ )
+ self.snapshot = async_to_raw_response_wrapper(
+ persistent_pty.snapshot,
+ )
+
+
+class PersistentPtyResourceWithStreamingResponse:
+ def __init__(self, persistent_pty: PersistentPtyResource) -> None:
+ self._persistent_pty = persistent_pty
+
+ self.create = to_streamed_response_wrapper(
+ persistent_pty.create,
+ )
+ self.update = to_streamed_response_wrapper(
+ persistent_pty.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ persistent_pty.list,
+ )
+ self.connect_token = to_streamed_response_wrapper(
+ persistent_pty.connect_token,
+ )
+ self.get = to_streamed_response_wrapper(
+ persistent_pty.get,
+ )
+ self.handoff = to_streamed_response_wrapper(
+ persistent_pty.handoff,
+ )
+ self.read = to_streamed_response_wrapper(
+ persistent_pty.read,
+ )
+ self.remove = to_streamed_response_wrapper(
+ persistent_pty.remove,
+ )
+ self.shutdown = to_streamed_response_wrapper(
+ persistent_pty.shutdown,
+ )
+ self.snapshot = to_streamed_response_wrapper(
+ persistent_pty.snapshot,
+ )
+
+
+class AsyncPersistentPtyResourceWithStreamingResponse:
+ def __init__(self, persistent_pty: AsyncPersistentPtyResource) -> None:
+ self._persistent_pty = persistent_pty
+
+ self.create = async_to_streamed_response_wrapper(
+ persistent_pty.create,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ persistent_pty.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ persistent_pty.list,
+ )
+ self.connect_token = async_to_streamed_response_wrapper(
+ persistent_pty.connect_token,
+ )
+ self.get = async_to_streamed_response_wrapper(
+ persistent_pty.get,
+ )
+ self.handoff = async_to_streamed_response_wrapper(
+ persistent_pty.handoff,
+ )
+ self.read = async_to_streamed_response_wrapper(
+ persistent_pty.read,
+ )
+ self.remove = async_to_streamed_response_wrapper(
+ persistent_pty.remove,
+ )
+ self.shutdown = async_to_streamed_response_wrapper(
+ persistent_pty.shutdown,
+ )
+ self.snapshot = async_to_streamed_response_wrapper(
+ persistent_pty.snapshot,
+ )
diff --git a/src/opencode/resources/file.py b/src/opencode/resources/file.py
index 4685232..7bc3fc5 100644
--- a/src/opencode/resources/file.py
+++ b/src/opencode/resources/file.py
@@ -1,426 +1,524 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``file`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from functools import cached_property
-from typing import TYPE_CHECKING, Literal, cast
-
-from .._base_client import RequestOptions
-from .._qs import encode_path
-from .._resource import AsyncAPIResource, SyncAPIResource
+import os
+from typing import Optional
+from typing_extensions import Literal
+
+import httpx
+
+from ..types import file_find_params, file_list_params, file_read_params, file_write_params
+from .._files import read_file_content, async_read_file_content
+from .._types import (
+ Body,
+ Omit,
+ Query,
+ Headers,
+ NoneType,
+ NotGiven,
+ BinaryTypes,
+ FileContent,
+ AsyncBinaryTypes,
+ omit,
+ not_given,
+)
+from .._utils import path_template, maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, OpenEnum, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- FileFindResponse,
- FileListResponse,
- FileWriteResponse,
- Location,
- )
+from .._base_client import make_request_options
+from ..types.file_find_response import FileFindResponse
+from ..types.file_list_response import FileListResponse
+from ..types.file_write_response import FileWriteResponse
__all__ = ["FileResource", "AsyncFileResource"]
class FileResource(SyncAPIResource):
- """Operations under ``client.file`` (OpenCode ``file``)."""
-
@cached_property
def with_raw_response(self) -> FileResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return FileResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> FileResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return FileResourceWithStreamingResponse(self)
- def read(
+ def list(
self,
- path: str,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[file_list_params.Location] | Omit = omit,
+ path: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> bytes:
- """Read file.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> FileListResponse:
+ """
+ List direct children using an absolute path or a path relative to the requested
+ location, including parents and siblings outside its directory. Entry paths
+ remain relative to the requested location; listing does not switch locations.
+
+ Args:
+ path: An absolute path or a path relative to the requested location. Defaults to the
+ location directory.
- Stream one file relative to the requested location with ETag, Last-Modified, and single-range HTTP Range support (206 Partial Content, 416 Range Not Satisfiable, and 304 Not Modified; multi-range and malformed Range headers fall back to 200).
+ extra_headers: Send extra headers
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- RemoteFileNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
+ extra_query: Add additional query parameters to the request
- ``GET /api/fs/read/*``
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "bytes",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/fs/read/{encode_path(path)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
+ return self._get(
+ "/api/fs/list",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "location": location,
+ "path": path,
+ },
+ file_list_params.FileListParams,
),
- kind="binary",
),
+ cast_to=FileListResponse,
)
- def list(
+ def find(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
- path: str | NotGiven | None = NOT_GIVEN,
+ query: str,
+ limit: Optional[int] | Omit = omit,
+ location: Optional[file_find_params.Location] | Omit = omit,
+ type: Optional[Literal["file", "directory"]] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> FileListResponse:
- """List directory.
-
- List direct children using an absolute path or a path relative to the requested location, including parents and siblings outside its directory. Entry paths remain relative to the requested location; listing does not switch locations.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> FileFindResponse:
+ """
+ Find recursively ranked filesystem entries relative to the requested location.
Args:
- path: An absolute path or a path relative to the requested location. Defaults to the location directory.
+ extra_headers: Send extra headers
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- RemoteFileNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
+ extra_query: Add additional query parameters to the request
- ``GET /api/fs/list``
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "FileListResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/fs/list",
- query={"location": self._location(location), "path": path},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
+ return self._get(
+ "/api/fs/find",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "query": query,
+ "limit": limit,
+ "location": location,
+ "type": type,
+ },
+ file_find_params.FileFindParams,
),
- response_type="FileListResponse",
),
+ cast_to=FileFindResponse,
)
- def find(
+ def read(
self,
+ path: str,
*,
- query: str,
- location: Location | NotGiven | None = NOT_GIVEN,
- type: OpenEnum[Literal["file", "directory"]] | NotGiven | None = NOT_GIVEN,
- limit: int | NotGiven | None = NOT_GIVEN,
+ location: Optional[file_read_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> FileFindResponse:
- """Find files.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Stream one file relative to the requested location with ETag, Last-Modified, and
+ single-range HTTP Range support (206 Partial Content, 416 Range Not Satisfiable,
+ and 304 Not Modified; multi-range and malformed Range headers fall back to 200).
- Find recursively ranked filesystem entries relative to the requested location.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ extra_body: Add additional JSON properties to the request
- ``GET /api/fs/find``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "FileFindResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/fs/find",
- query={"location": self._location(location), "query": query, "type": type, "limit": limit},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="FileFindResponse",
+ if not path:
+ raise ValueError(f"Expected a non-empty value for `path` but received {path!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._get(
+ path_template("/api/fs/read/{path}", path=path),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, file_read_params.FileReadParams),
),
+ cast_to=NoneType,
)
def write(
self,
*,
+ body: FileContent | BinaryTypes,
path: str,
- payload: bytes,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[file_write_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> FileWriteResponse:
- """Write file.
+ """
+ Write the raw request body to an absolute path or a path relative to the
+ requested location, creating parent directories, and return the resolved
+ absolute path. Unlike read, the target is not confined to the location.
+ Experimental: may change without compatibility guarantees.
- Write the raw request body to an absolute path or a path relative to the requested location, creating parent directories, and return the resolved absolute path. Unlike read, the target is not confined to the location. Experimental: may change without compatibility guarantees.
+ Args:
+ path: An absolute path or a path relative to the requested location. Missing parent
+ directories are created.
- This endpoint is experimental in OpenCode and may change without notice.
+ extra_headers: Send extra headers
- Args:
- path: An absolute path or a path relative to the requested location. Missing parent directories are created.
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ extra_body: Add additional JSON properties to the request
- ``POST /api/experimental/fs/write``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "FileWriteResponse",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/experimental/fs/write",
- query={"location": self._location(location), "path": path},
- content=bytes(payload),
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
+ extra_headers = {"Content-Type": "application/octet-stream", **(extra_headers or {})}
+ return self._post(
+ "/api/experimental/fs/write",
+ content=read_file_content(body) if isinstance(body, os.PathLike) else body,
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "path": path,
+ "location": location,
+ },
+ file_write_params.FileWriteParams,
),
- response_type="FileWriteResponse",
),
+ cast_to=FileWriteResponse,
)
-class FileResourceWithRawResponse:
- """``client.file.with_raw_response``."""
-
- def __init__(self, resource: FileResource) -> None:
- self._resource = resource
- self.read = to_raw_response_wrapper(resource.read)
- self.list = to_raw_response_wrapper(resource.list)
- self.find = to_raw_response_wrapper(resource.find)
- self.write = to_raw_response_wrapper(resource.write)
-
-
-class FileResourceWithStreamingResponse:
- """``client.file.with_streaming_response``."""
-
- def __init__(self, resource: FileResource) -> None:
- self._resource = resource
- self.read = to_streamed_response_wrapper(resource.read)
- self.list = to_streamed_response_wrapper(resource.list)
- self.find = to_streamed_response_wrapper(resource.find)
- self.write = to_streamed_response_wrapper(resource.write)
-
-
class AsyncFileResource(AsyncAPIResource):
- """Operations under ``client.file`` (OpenCode ``file``)."""
-
@cached_property
def with_raw_response(self) -> AsyncFileResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncFileResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncFileResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncFileResourceWithStreamingResponse(self)
- async def read(
+ async def list(
self,
- path: str,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[file_list_params.Location] | Omit = omit,
+ path: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> bytes:
- """Read file.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> FileListResponse:
+ """
+ List direct children using an absolute path or a path relative to the requested
+ location, including parents and siblings outside its directory. Entry paths
+ remain relative to the requested location; listing does not switch locations.
- Stream one file relative to the requested location with ETag, Last-Modified, and single-range HTTP Range support (206 Partial Content, 416 Range Not Satisfiable, and 304 Not Modified; multi-range and malformed Range headers fall back to 200).
+ Args:
+ path: An absolute path or a path relative to the requested location. Defaults to the
+ location directory.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- RemoteFileNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
+ extra_headers: Send extra headers
- ``GET /api/fs/read/*``
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "bytes",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/fs/read/{encode_path(path)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
+ return await self._get(
+ "/api/fs/list",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "location": location,
+ "path": path,
+ },
+ file_list_params.FileListParams,
),
- kind="binary",
),
+ cast_to=FileListResponse,
)
- async def list(
+ async def find(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
- path: str | NotGiven | None = NOT_GIVEN,
+ query: str,
+ limit: Optional[int] | Omit = omit,
+ location: Optional[file_find_params.Location] | Omit = omit,
+ type: Optional[Literal["file", "directory"]] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> FileListResponse:
- """List directory.
-
- List direct children using an absolute path or a path relative to the requested location, including parents and siblings outside its directory. Entry paths remain relative to the requested location; listing does not switch locations.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> FileFindResponse:
+ """
+ Find recursively ranked filesystem entries relative to the requested location.
Args:
- path: An absolute path or a path relative to the requested location. Defaults to the location directory.
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- RemoteFileNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
+ extra_body: Add additional JSON properties to the request
- ``GET /api/fs/list``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "FileListResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/fs/list",
- query={"location": self._location(location), "path": path},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
+ return await self._get(
+ "/api/fs/find",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "query": query,
+ "limit": limit,
+ "location": location,
+ "type": type,
+ },
+ file_find_params.FileFindParams,
),
- response_type="FileListResponse",
),
+ cast_to=FileFindResponse,
)
- async def find(
+ async def read(
self,
+ path: str,
*,
- query: str,
- location: Location | NotGiven | None = NOT_GIVEN,
- type: OpenEnum[Literal["file", "directory"]] | NotGiven | None = NOT_GIVEN,
- limit: int | NotGiven | None = NOT_GIVEN,
+ location: Optional[file_read_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> FileFindResponse:
- """Find files.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Stream one file relative to the requested location with ETag, Last-Modified, and
+ single-range HTTP Range support (206 Partial Content, 416 Range Not Satisfiable,
+ and 304 Not Modified; multi-range and malformed Range headers fall back to 200).
- Find recursively ranked filesystem entries relative to the requested location.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ extra_body: Add additional JSON properties to the request
- ``GET /api/fs/find``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "FileFindResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/fs/find",
- query={"location": self._location(location), "query": query, "type": type, "limit": limit},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="FileFindResponse",
+ if not path:
+ raise ValueError(f"Expected a non-empty value for `path` but received {path!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._get(
+ path_template("/api/fs/read/{path}", path=path),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, file_read_params.FileReadParams),
),
+ cast_to=NoneType,
)
async def write(
self,
*,
+ body: FileContent | AsyncBinaryTypes,
path: str,
- payload: bytes,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[file_write_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> FileWriteResponse:
- """Write file.
+ """
+ Write the raw request body to an absolute path or a path relative to the
+ requested location, creating parent directories, and return the resolved
+ absolute path. Unlike read, the target is not confined to the location.
+ Experimental: may change without compatibility guarantees.
- Write the raw request body to an absolute path or a path relative to the requested location, creating parent directories, and return the resolved absolute path. Unlike read, the target is not confined to the location. Experimental: may change without compatibility guarantees.
+ Args:
+ path: An absolute path or a path relative to the requested location. Missing parent
+ directories are created.
- This endpoint is experimental in OpenCode and may change without notice.
+ extra_headers: Send extra headers
- Args:
- path: An absolute path or a path relative to the requested location. Missing parent directories are created.
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ extra_body: Add additional JSON properties to the request
- ``POST /api/experimental/fs/write``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "FileWriteResponse",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/experimental/fs/write",
- query={"location": self._location(location), "path": path},
- content=bytes(payload),
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
+ extra_headers = {"Content-Type": "application/octet-stream", **(extra_headers or {})}
+ return await self._post(
+ "/api/experimental/fs/write",
+ content=await async_read_file_content(body) if isinstance(body, os.PathLike) else body,
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "path": path,
+ "location": location,
+ },
+ file_write_params.FileWriteParams,
),
- response_type="FileWriteResponse",
),
+ cast_to=FileWriteResponse,
+ )
+
+
+class FileResourceWithRawResponse:
+ def __init__(self, file: FileResource) -> None:
+ self._file = file
+
+ self.list = to_raw_response_wrapper(
+ file.list,
+ )
+ self.find = to_raw_response_wrapper(
+ file.find,
+ )
+ self.read = to_raw_response_wrapper(
+ file.read,
+ )
+ self.write = to_raw_response_wrapper(
+ file.write,
)
class AsyncFileResourceWithRawResponse:
- """``client.file.with_raw_response``."""
+ def __init__(self, file: AsyncFileResource) -> None:
+ self._file = file
+
+ self.list = async_to_raw_response_wrapper(
+ file.list,
+ )
+ self.find = async_to_raw_response_wrapper(
+ file.find,
+ )
+ self.read = async_to_raw_response_wrapper(
+ file.read,
+ )
+ self.write = async_to_raw_response_wrapper(
+ file.write,
+ )
- def __init__(self, resource: AsyncFileResource) -> None:
- self._resource = resource
- self.read = async_to_raw_response_wrapper(resource.read)
- self.list = async_to_raw_response_wrapper(resource.list)
- self.find = async_to_raw_response_wrapper(resource.find)
- self.write = async_to_raw_response_wrapper(resource.write)
+
+class FileResourceWithStreamingResponse:
+ def __init__(self, file: FileResource) -> None:
+ self._file = file
+
+ self.list = to_streamed_response_wrapper(
+ file.list,
+ )
+ self.find = to_streamed_response_wrapper(
+ file.find,
+ )
+ self.read = to_streamed_response_wrapper(
+ file.read,
+ )
+ self.write = to_streamed_response_wrapper(
+ file.write,
+ )
class AsyncFileResourceWithStreamingResponse:
- """``client.file.with_streaming_response``."""
-
- def __init__(self, resource: AsyncFileResource) -> None:
- self._resource = resource
- self.read = async_to_streamed_response_wrapper(resource.read)
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.find = async_to_streamed_response_wrapper(resource.find)
- self.write = async_to_streamed_response_wrapper(resource.write)
+ def __init__(self, file: AsyncFileResource) -> None:
+ self._file = file
+
+ self.list = async_to_streamed_response_wrapper(
+ file.list,
+ )
+ self.find = async_to_streamed_response_wrapper(
+ file.find,
+ )
+ self.read = async_to_streamed_response_wrapper(
+ file.read,
+ )
+ self.write = async_to_streamed_response_wrapper(
+ file.write,
+ )
diff --git a/src/opencode/resources/form.py b/src/opencode/resources/form.py
index 005257e..b0c11bb 100644
--- a/src/opencode/resources/form.py
+++ b/src/opencode/resources/form.py
@@ -1,159 +1,171 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``form`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
+from typing import Optional
+
+import httpx
-from .._base_client import RequestOptions
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import form_list_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- FormListResponse,
- Location,
- )
+from .._base_client import make_request_options
+from ..types.form_list_response import FormListResponse
__all__ = ["FormResource", "AsyncFormResource"]
class FormResource(SyncAPIResource):
- """Operations under ``client.form`` (OpenCode ``form``)."""
-
@cached_property
def with_raw_response(self) -> FormResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return FormResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> FormResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return FormResourceWithStreamingResponse(self)
def list(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[form_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> FormListResponse:
- """List pending forms.
-
+ """
Retrieve pending forms for a location.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ Args:
+ extra_headers: Send extra headers
- ``GET /api/form``
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "FormListResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/form",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="FormListResponse",
+ return self._get(
+ "/api/form",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, form_list_params.FormListParams),
),
+ cast_to=FormListResponse,
)
-class FormResourceWithRawResponse:
- """``client.form.with_raw_response``."""
-
- def __init__(self, resource: FormResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
-
-
-class FormResourceWithStreamingResponse:
- """``client.form.with_streaming_response``."""
-
- def __init__(self, resource: FormResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
-
-
class AsyncFormResource(AsyncAPIResource):
- """Operations under ``client.form`` (OpenCode ``form``)."""
-
@cached_property
def with_raw_response(self) -> AsyncFormResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncFormResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncFormResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncFormResourceWithStreamingResponse(self)
async def list(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[form_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> FormListResponse:
- """List pending forms.
-
+ """
Retrieve pending forms for a location.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
- ``GET /api/form``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "FormListResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/form",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="FormListResponse",
+ return await self._get(
+ "/api/form",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, form_list_params.FormListParams),
),
+ cast_to=FormListResponse,
+ )
+
+
+class FormResourceWithRawResponse:
+ def __init__(self, form: FormResource) -> None:
+ self._form = form
+
+ self.list = to_raw_response_wrapper(
+ form.list,
)
class AsyncFormResourceWithRawResponse:
- """``client.form.with_raw_response``."""
+ def __init__(self, form: AsyncFormResource) -> None:
+ self._form = form
+
+ self.list = async_to_raw_response_wrapper(
+ form.list,
+ )
+
+
+class FormResourceWithStreamingResponse:
+ def __init__(self, form: FormResource) -> None:
+ self._form = form
- def __init__(self, resource: AsyncFormResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
+ self.list = to_streamed_response_wrapper(
+ form.list,
+ )
class AsyncFormResourceWithStreamingResponse:
- """``client.form.with_streaming_response``."""
+ def __init__(self, form: AsyncFormResource) -> None:
+ self._form = form
- def __init__(self, resource: AsyncFormResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
+ self.list = async_to_streamed_response_wrapper(
+ form.list,
+ )
diff --git a/src/opencode/resources/generate.py b/src/opencode/resources/generate.py
index 17df697..e9c92fc 100644
--- a/src/opencode/resources/generate.py
+++ b/src/opencode/resources/generate.py
@@ -1,169 +1,184 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``generate`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
+from typing import Optional
+
+import httpx
-from .._base_client import RequestOptions
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import generate_text_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- GenerateTextResponseData,
- ModelRef,
- )
+from .._base_client import make_request_options
+from ..types.shared.model_ref import ModelRef
+from ..types.shared.generate_text_response import GenerateTextResponse
__all__ = ["GenerateResource", "AsyncGenerateResource"]
class GenerateResource(SyncAPIResource):
- """Operations under ``client.generate`` (OpenCode ``generate``)."""
-
@cached_property
def with_raw_response(self) -> GenerateResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return GenerateResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> GenerateResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return GenerateResourceWithStreamingResponse(self)
def text(
self,
*,
prompt: str,
- model: ModelRef | NotGiven | None = NOT_GIVEN,
+ model: Optional[ModelRef] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> GenerateTextResponseData:
- """Generate text.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> GenerateTextResponse:
+ """
+ Run one stateless model generation using the server's base configuration and
+ return the assistant text. Uses the base configuration's default model when none
+ is specified.
- Run one stateless model generation using the server's base configuration and return the assistant text. Uses the base configuration's default model when none is specified.
+ Args:
+ extra_headers: Send extra headers
- This endpoint is experimental in OpenCode and may change without notice.
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
+ extra_body: Add additional JSON properties to the request
- ``POST /api/experimental/generate``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "GenerateTextResponseData",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/experimental/generate",
- body={"prompt": prompt, "model": model},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="GenerateTextResponseData",
- unwrap=True,
+ return self._post(
+ "/api/experimental/generate",
+ body=maybe_transform(
+ {
+ "prompt": prompt,
+ "model": model,
+ },
+ generate_text_params.GenerateTextParams,
),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=GenerateTextResponse,
)
-class GenerateResourceWithRawResponse:
- """``client.generate.with_raw_response``."""
-
- def __init__(self, resource: GenerateResource) -> None:
- self._resource = resource
- self.text = to_raw_response_wrapper(resource.text)
-
-
-class GenerateResourceWithStreamingResponse:
- """``client.generate.with_streaming_response``."""
-
- def __init__(self, resource: GenerateResource) -> None:
- self._resource = resource
- self.text = to_streamed_response_wrapper(resource.text)
-
-
class AsyncGenerateResource(AsyncAPIResource):
- """Operations under ``client.generate`` (OpenCode ``generate``)."""
-
@cached_property
def with_raw_response(self) -> AsyncGenerateResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncGenerateResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncGenerateResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncGenerateResourceWithStreamingResponse(self)
async def text(
self,
*,
prompt: str,
- model: ModelRef | NotGiven | None = NOT_GIVEN,
+ model: Optional[ModelRef] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> GenerateTextResponseData:
- """Generate text.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> GenerateTextResponse:
+ """
+ Run one stateless model generation using the server's base configuration and
+ return the assistant text. Uses the base configuration's default model when none
+ is specified.
- Run one stateless model generation using the server's base configuration and return the assistant text. Uses the base configuration's default model when none is specified.
+ Args:
+ extra_headers: Send extra headers
- This endpoint is experimental in OpenCode and may change without notice.
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
+ extra_body: Add additional JSON properties to the request
- ``POST /api/experimental/generate``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "GenerateTextResponseData",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/experimental/generate",
- body={"prompt": prompt, "model": model},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="GenerateTextResponseData",
- unwrap=True,
+ return await self._post(
+ "/api/experimental/generate",
+ body=await async_maybe_transform(
+ {
+ "prompt": prompt,
+ "model": model,
+ },
+ generate_text_params.GenerateTextParams,
),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=GenerateTextResponse,
+ )
+
+
+class GenerateResourceWithRawResponse:
+ def __init__(self, generate: GenerateResource) -> None:
+ self._generate = generate
+
+ self.text = to_raw_response_wrapper(
+ generate.text,
)
class AsyncGenerateResourceWithRawResponse:
- """``client.generate.with_raw_response``."""
+ def __init__(self, generate: AsyncGenerateResource) -> None:
+ self._generate = generate
- def __init__(self, resource: AsyncGenerateResource) -> None:
- self._resource = resource
- self.text = async_to_raw_response_wrapper(resource.text)
+ self.text = async_to_raw_response_wrapper(
+ generate.text,
+ )
+
+
+class GenerateResourceWithStreamingResponse:
+ def __init__(self, generate: GenerateResource) -> None:
+ self._generate = generate
+
+ self.text = to_streamed_response_wrapper(
+ generate.text,
+ )
class AsyncGenerateResourceWithStreamingResponse:
- """``client.generate.with_streaming_response``."""
+ def __init__(self, generate: AsyncGenerateResource) -> None:
+ self._generate = generate
- def __init__(self, resource: AsyncGenerateResource) -> None:
- self._resource = resource
- self.text = async_to_streamed_response_wrapper(resource.text)
+ self.text = async_to_streamed_response_wrapper(
+ generate.text,
+ )
diff --git a/src/opencode/resources/integration/__init__.py b/src/opencode/resources/integration/__init__.py
new file mode 100644
index 0000000..52b3312
--- /dev/null
+++ b/src/opencode/resources/integration/__init__.py
@@ -0,0 +1,75 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from .oauth import (
+ OauthResource,
+ AsyncOauthResource,
+ OauthResourceWithRawResponse,
+ AsyncOauthResourceWithRawResponse,
+ OauthResourceWithStreamingResponse,
+ AsyncOauthResourceWithStreamingResponse,
+)
+from .command import (
+ CommandResource,
+ AsyncCommandResource,
+ CommandResourceWithRawResponse,
+ AsyncCommandResourceWithRawResponse,
+ CommandResourceWithStreamingResponse,
+ AsyncCommandResourceWithStreamingResponse,
+)
+from .connect import (
+ ConnectResource,
+ AsyncConnectResource,
+ ConnectResourceWithRawResponse,
+ AsyncConnectResourceWithRawResponse,
+ ConnectResourceWithStreamingResponse,
+ AsyncConnectResourceWithStreamingResponse,
+)
+from .wellknown import (
+ WellknownResource,
+ AsyncWellknownResource,
+ WellknownResourceWithRawResponse,
+ AsyncWellknownResourceWithRawResponse,
+ WellknownResourceWithStreamingResponse,
+ AsyncWellknownResourceWithStreamingResponse,
+)
+from .integration import (
+ IntegrationResource,
+ AsyncIntegrationResource,
+ IntegrationResourceWithRawResponse,
+ AsyncIntegrationResourceWithRawResponse,
+ IntegrationResourceWithStreamingResponse,
+ AsyncIntegrationResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "WellknownResource",
+ "AsyncWellknownResource",
+ "WellknownResourceWithRawResponse",
+ "AsyncWellknownResourceWithRawResponse",
+ "WellknownResourceWithStreamingResponse",
+ "AsyncWellknownResourceWithStreamingResponse",
+ "ConnectResource",
+ "AsyncConnectResource",
+ "ConnectResourceWithRawResponse",
+ "AsyncConnectResourceWithRawResponse",
+ "ConnectResourceWithStreamingResponse",
+ "AsyncConnectResourceWithStreamingResponse",
+ "OauthResource",
+ "AsyncOauthResource",
+ "OauthResourceWithRawResponse",
+ "AsyncOauthResourceWithRawResponse",
+ "OauthResourceWithStreamingResponse",
+ "AsyncOauthResourceWithStreamingResponse",
+ "CommandResource",
+ "AsyncCommandResource",
+ "CommandResourceWithRawResponse",
+ "AsyncCommandResourceWithRawResponse",
+ "CommandResourceWithStreamingResponse",
+ "AsyncCommandResourceWithStreamingResponse",
+ "IntegrationResource",
+ "AsyncIntegrationResource",
+ "IntegrationResourceWithRawResponse",
+ "AsyncIntegrationResourceWithRawResponse",
+ "IntegrationResourceWithStreamingResponse",
+ "AsyncIntegrationResourceWithStreamingResponse",
+]
diff --git a/src/opencode/resources/integration/command.py b/src/opencode/resources/integration/command.py
new file mode 100644
index 0000000..b184ffa
--- /dev/null
+++ b/src/opencode/resources/integration/command.py
@@ -0,0 +1,405 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.integration import command_cancel_params, command_status_params, command_connect_params
+from ...types.shared.integration_id import IntegrationID
+from ...types.shared.integration_method_id import IntegrationMethodID
+from ...types.shared.integration_attempt_id import IntegrationAttemptID
+from ...types.integration.command_status_response import CommandStatusResponse
+from ...types.integration.command_connect_response import CommandConnectResponse
+
+__all__ = ["CommandResource", "AsyncCommandResource"]
+
+
+class CommandResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> CommandResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return CommandResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> CommandResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return CommandResourceWithStreamingResponse(self)
+
+ def cancel(
+ self,
+ attempt_id: IntegrationAttemptID,
+ *,
+ integration_id: IntegrationID,
+ location: Optional[command_cancel_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Cancel a command authentication attempt and terminate its process.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ if not attempt_id:
+ raise ValueError(f"Expected a non-empty value for `attempt_id` but received {attempt_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template(
+ "/api/integration/{integration_id}/connect/command/{attempt_id}",
+ integration_id=integration_id,
+ attempt_id=attempt_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, command_cancel_params.CommandCancelParams),
+ ),
+ cast_to=NoneType,
+ )
+
+ def connect(
+ self,
+ integration_id: IntegrationID,
+ *,
+ method_id: IntegrationMethodID,
+ label: Optional[str] | Omit = omit,
+ location: Optional[command_connect_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> CommandConnectResponse:
+ """
+ Start a command authentication attempt.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ return self._post(
+ path_template("/api/integration/{integration_id}/connect/command", integration_id=integration_id),
+ body=maybe_transform(
+ {
+ "method_id": method_id,
+ "label": label,
+ },
+ command_connect_params.CommandConnectParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, command_connect_params.CommandConnectParams),
+ ),
+ cast_to=CommandConnectResponse,
+ )
+
+ def status(
+ self,
+ attempt_id: IntegrationAttemptID,
+ *,
+ integration_id: IntegrationID,
+ location: Optional[command_status_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> CommandStatusResponse:
+ """
+ Poll the current status and output of a command authentication attempt.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ if not attempt_id:
+ raise ValueError(f"Expected a non-empty value for `attempt_id` but received {attempt_id!r}")
+ return self._get(
+ path_template(
+ "/api/integration/{integration_id}/connect/command/{attempt_id}",
+ integration_id=integration_id,
+ attempt_id=attempt_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, command_status_params.CommandStatusParams),
+ ),
+ cast_to=CommandStatusResponse,
+ )
+
+
+class AsyncCommandResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncCommandResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncCommandResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncCommandResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncCommandResourceWithStreamingResponse(self)
+
+ async def cancel(
+ self,
+ attempt_id: IntegrationAttemptID,
+ *,
+ integration_id: IntegrationID,
+ location: Optional[command_cancel_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Cancel a command authentication attempt and terminate its process.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ if not attempt_id:
+ raise ValueError(f"Expected a non-empty value for `attempt_id` but received {attempt_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template(
+ "/api/integration/{integration_id}/connect/command/{attempt_id}",
+ integration_id=integration_id,
+ attempt_id=attempt_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, command_cancel_params.CommandCancelParams),
+ ),
+ cast_to=NoneType,
+ )
+
+ async def connect(
+ self,
+ integration_id: IntegrationID,
+ *,
+ method_id: IntegrationMethodID,
+ label: Optional[str] | Omit = omit,
+ location: Optional[command_connect_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> CommandConnectResponse:
+ """
+ Start a command authentication attempt.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ return await self._post(
+ path_template("/api/integration/{integration_id}/connect/command", integration_id=integration_id),
+ body=await async_maybe_transform(
+ {
+ "method_id": method_id,
+ "label": label,
+ },
+ command_connect_params.CommandConnectParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, command_connect_params.CommandConnectParams),
+ ),
+ cast_to=CommandConnectResponse,
+ )
+
+ async def status(
+ self,
+ attempt_id: IntegrationAttemptID,
+ *,
+ integration_id: IntegrationID,
+ location: Optional[command_status_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> CommandStatusResponse:
+ """
+ Poll the current status and output of a command authentication attempt.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ if not attempt_id:
+ raise ValueError(f"Expected a non-empty value for `attempt_id` but received {attempt_id!r}")
+ return await self._get(
+ path_template(
+ "/api/integration/{integration_id}/connect/command/{attempt_id}",
+ integration_id=integration_id,
+ attempt_id=attempt_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, command_status_params.CommandStatusParams),
+ ),
+ cast_to=CommandStatusResponse,
+ )
+
+
+class CommandResourceWithRawResponse:
+ def __init__(self, command: CommandResource) -> None:
+ self._command = command
+
+ self.cancel = to_raw_response_wrapper(
+ command.cancel,
+ )
+ self.connect = to_raw_response_wrapper(
+ command.connect,
+ )
+ self.status = to_raw_response_wrapper(
+ command.status,
+ )
+
+
+class AsyncCommandResourceWithRawResponse:
+ def __init__(self, command: AsyncCommandResource) -> None:
+ self._command = command
+
+ self.cancel = async_to_raw_response_wrapper(
+ command.cancel,
+ )
+ self.connect = async_to_raw_response_wrapper(
+ command.connect,
+ )
+ self.status = async_to_raw_response_wrapper(
+ command.status,
+ )
+
+
+class CommandResourceWithStreamingResponse:
+ def __init__(self, command: CommandResource) -> None:
+ self._command = command
+
+ self.cancel = to_streamed_response_wrapper(
+ command.cancel,
+ )
+ self.connect = to_streamed_response_wrapper(
+ command.connect,
+ )
+ self.status = to_streamed_response_wrapper(
+ command.status,
+ )
+
+
+class AsyncCommandResourceWithStreamingResponse:
+ def __init__(self, command: AsyncCommandResource) -> None:
+ self._command = command
+
+ self.cancel = async_to_streamed_response_wrapper(
+ command.cancel,
+ )
+ self.connect = async_to_streamed_response_wrapper(
+ command.connect,
+ )
+ self.status = async_to_streamed_response_wrapper(
+ command.status,
+ )
diff --git a/src/opencode/resources/integration/connect.py b/src/opencode/resources/integration/connect.py
new file mode 100644
index 0000000..0bcff9e
--- /dev/null
+++ b/src/opencode/resources/integration/connect.py
@@ -0,0 +1,319 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.integration import connect_key_params, connect_external_params
+from ...types.shared.form_answer import FormAnswer
+from ...types.shared.integration_id import IntegrationID
+from ...types.shared.integration_method_id import IntegrationMethodID
+
+__all__ = ["ConnectResource", "AsyncConnectResource"]
+
+
+class ConnectResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> ConnectResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return ConnectResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ConnectResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return ConnectResourceWithStreamingResponse(self)
+
+ def external(
+ self,
+ integration_id: IntegrationID,
+ *,
+ method_id: IntegrationMethodID,
+ answer: Optional[FormAnswer] | Omit = omit,
+ label: Optional[str] | Omit = omit,
+ location: Optional[connect_external_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Run an external authentication method and store a reference to its credential
+ source.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template("/api/integration/{integration_id}/connect/external", integration_id=integration_id),
+ body=maybe_transform(
+ {
+ "method_id": method_id,
+ "answer": answer,
+ "label": label,
+ },
+ connect_external_params.ConnectExternalParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, connect_external_params.ConnectExternalParams),
+ ),
+ cast_to=NoneType,
+ )
+
+ def key(
+ self,
+ integration_id: IntegrationID,
+ *,
+ key: str,
+ answer: Optional[FormAnswer] | Omit = omit,
+ label: Optional[str] | Omit = omit,
+ location: Optional[connect_key_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Run a key authentication method and store the resulting credential.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template("/api/integration/{integration_id}/connect/key", integration_id=integration_id),
+ body=maybe_transform(
+ {
+ "key": key,
+ "answer": answer,
+ "label": label,
+ },
+ connect_key_params.ConnectKeyParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, connect_key_params.ConnectKeyParams),
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncConnectResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncConnectResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncConnectResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncConnectResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncConnectResourceWithStreamingResponse(self)
+
+ async def external(
+ self,
+ integration_id: IntegrationID,
+ *,
+ method_id: IntegrationMethodID,
+ answer: Optional[FormAnswer] | Omit = omit,
+ label: Optional[str] | Omit = omit,
+ location: Optional[connect_external_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Run an external authentication method and store a reference to its credential
+ source.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template("/api/integration/{integration_id}/connect/external", integration_id=integration_id),
+ body=await async_maybe_transform(
+ {
+ "method_id": method_id,
+ "answer": answer,
+ "label": label,
+ },
+ connect_external_params.ConnectExternalParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"location": location}, connect_external_params.ConnectExternalParams
+ ),
+ ),
+ cast_to=NoneType,
+ )
+
+ async def key(
+ self,
+ integration_id: IntegrationID,
+ *,
+ key: str,
+ answer: Optional[FormAnswer] | Omit = omit,
+ label: Optional[str] | Omit = omit,
+ location: Optional[connect_key_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Run a key authentication method and store the resulting credential.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template("/api/integration/{integration_id}/connect/key", integration_id=integration_id),
+ body=await async_maybe_transform(
+ {
+ "key": key,
+ "answer": answer,
+ "label": label,
+ },
+ connect_key_params.ConnectKeyParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, connect_key_params.ConnectKeyParams),
+ ),
+ cast_to=NoneType,
+ )
+
+
+class ConnectResourceWithRawResponse:
+ def __init__(self, connect: ConnectResource) -> None:
+ self._connect = connect
+
+ self.external = to_raw_response_wrapper(
+ connect.external,
+ )
+ self.key = to_raw_response_wrapper(
+ connect.key,
+ )
+
+
+class AsyncConnectResourceWithRawResponse:
+ def __init__(self, connect: AsyncConnectResource) -> None:
+ self._connect = connect
+
+ self.external = async_to_raw_response_wrapper(
+ connect.external,
+ )
+ self.key = async_to_raw_response_wrapper(
+ connect.key,
+ )
+
+
+class ConnectResourceWithStreamingResponse:
+ def __init__(self, connect: ConnectResource) -> None:
+ self._connect = connect
+
+ self.external = to_streamed_response_wrapper(
+ connect.external,
+ )
+ self.key = to_streamed_response_wrapper(
+ connect.key,
+ )
+
+
+class AsyncConnectResourceWithStreamingResponse:
+ def __init__(self, connect: AsyncConnectResource) -> None:
+ self._connect = connect
+
+ self.external = async_to_streamed_response_wrapper(
+ connect.external,
+ )
+ self.key = async_to_streamed_response_wrapper(
+ connect.key,
+ )
diff --git a/src/opencode/resources/integration/integration.py b/src/opencode/resources/integration/integration.py
new file mode 100644
index 0000000..371be83
--- /dev/null
+++ b/src/opencode/resources/integration/integration.py
@@ -0,0 +1,391 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from .oauth import (
+ OauthResource,
+ AsyncOauthResource,
+ OauthResourceWithRawResponse,
+ AsyncOauthResourceWithRawResponse,
+ OauthResourceWithStreamingResponse,
+ AsyncOauthResourceWithStreamingResponse,
+)
+from ...types import integration_get_params, integration_list_params
+from .command import (
+ CommandResource,
+ AsyncCommandResource,
+ CommandResourceWithRawResponse,
+ AsyncCommandResourceWithRawResponse,
+ CommandResourceWithStreamingResponse,
+ AsyncCommandResourceWithStreamingResponse,
+)
+from .connect import (
+ ConnectResource,
+ AsyncConnectResource,
+ ConnectResourceWithRawResponse,
+ AsyncConnectResourceWithRawResponse,
+ ConnectResourceWithStreamingResponse,
+ AsyncConnectResourceWithStreamingResponse,
+)
+from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from .wellknown import (
+ WellknownResource,
+ AsyncWellknownResource,
+ WellknownResourceWithRawResponse,
+ AsyncWellknownResourceWithRawResponse,
+ WellknownResourceWithStreamingResponse,
+ AsyncWellknownResourceWithStreamingResponse,
+)
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.shared.integration_id import IntegrationID
+from ...types.integration_get_response import IntegrationGetResponse
+from ...types.integration_list_response import IntegrationListResponse
+
+__all__ = ["IntegrationResource", "AsyncIntegrationResource"]
+
+
+class IntegrationResource(SyncAPIResource):
+ @cached_property
+ def wellknown(self) -> WellknownResource:
+ return WellknownResource(self._client)
+
+ @cached_property
+ def connect(self) -> ConnectResource:
+ return ConnectResource(self._client)
+
+ @cached_property
+ def oauth(self) -> OauthResource:
+ return OauthResource(self._client)
+
+ @cached_property
+ def command(self) -> CommandResource:
+ return CommandResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> IntegrationResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return IntegrationResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> IntegrationResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return IntegrationResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ *,
+ location: Optional[integration_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> IntegrationListResponse:
+ """
+ Retrieve available integrations and their authentication methods.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/integration",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, integration_list_params.IntegrationListParams),
+ ),
+ cast_to=IntegrationListResponse,
+ )
+
+ def get(
+ self,
+ integration_id: IntegrationID,
+ *,
+ location: Optional[integration_get_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> IntegrationGetResponse:
+ """
+ Retrieve one integration and its authentication methods.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ return self._get(
+ path_template("/api/integration/{integration_id}", integration_id=integration_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, integration_get_params.IntegrationGetParams),
+ ),
+ cast_to=IntegrationGetResponse,
+ )
+
+
+class AsyncIntegrationResource(AsyncAPIResource):
+ @cached_property
+ def wellknown(self) -> AsyncWellknownResource:
+ return AsyncWellknownResource(self._client)
+
+ @cached_property
+ def connect(self) -> AsyncConnectResource:
+ return AsyncConnectResource(self._client)
+
+ @cached_property
+ def oauth(self) -> AsyncOauthResource:
+ return AsyncOauthResource(self._client)
+
+ @cached_property
+ def command(self) -> AsyncCommandResource:
+ return AsyncCommandResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncIntegrationResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncIntegrationResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncIntegrationResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncIntegrationResourceWithStreamingResponse(self)
+
+ async def list(
+ self,
+ *,
+ location: Optional[integration_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> IntegrationListResponse:
+ """
+ Retrieve available integrations and their authentication methods.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/integration",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"location": location}, integration_list_params.IntegrationListParams
+ ),
+ ),
+ cast_to=IntegrationListResponse,
+ )
+
+ async def get(
+ self,
+ integration_id: IntegrationID,
+ *,
+ location: Optional[integration_get_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> IntegrationGetResponse:
+ """
+ Retrieve one integration and its authentication methods.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ return await self._get(
+ path_template("/api/integration/{integration_id}", integration_id=integration_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, integration_get_params.IntegrationGetParams),
+ ),
+ cast_to=IntegrationGetResponse,
+ )
+
+
+class IntegrationResourceWithRawResponse:
+ def __init__(self, integration: IntegrationResource) -> None:
+ self._integration = integration
+
+ self.list = to_raw_response_wrapper(
+ integration.list,
+ )
+ self.get = to_raw_response_wrapper(
+ integration.get,
+ )
+
+ @cached_property
+ def wellknown(self) -> WellknownResourceWithRawResponse:
+ return WellknownResourceWithRawResponse(self._integration.wellknown)
+
+ @cached_property
+ def connect(self) -> ConnectResourceWithRawResponse:
+ return ConnectResourceWithRawResponse(self._integration.connect)
+
+ @cached_property
+ def oauth(self) -> OauthResourceWithRawResponse:
+ return OauthResourceWithRawResponse(self._integration.oauth)
+
+ @cached_property
+ def command(self) -> CommandResourceWithRawResponse:
+ return CommandResourceWithRawResponse(self._integration.command)
+
+
+class AsyncIntegrationResourceWithRawResponse:
+ def __init__(self, integration: AsyncIntegrationResource) -> None:
+ self._integration = integration
+
+ self.list = async_to_raw_response_wrapper(
+ integration.list,
+ )
+ self.get = async_to_raw_response_wrapper(
+ integration.get,
+ )
+
+ @cached_property
+ def wellknown(self) -> AsyncWellknownResourceWithRawResponse:
+ return AsyncWellknownResourceWithRawResponse(self._integration.wellknown)
+
+ @cached_property
+ def connect(self) -> AsyncConnectResourceWithRawResponse:
+ return AsyncConnectResourceWithRawResponse(self._integration.connect)
+
+ @cached_property
+ def oauth(self) -> AsyncOauthResourceWithRawResponse:
+ return AsyncOauthResourceWithRawResponse(self._integration.oauth)
+
+ @cached_property
+ def command(self) -> AsyncCommandResourceWithRawResponse:
+ return AsyncCommandResourceWithRawResponse(self._integration.command)
+
+
+class IntegrationResourceWithStreamingResponse:
+ def __init__(self, integration: IntegrationResource) -> None:
+ self._integration = integration
+
+ self.list = to_streamed_response_wrapper(
+ integration.list,
+ )
+ self.get = to_streamed_response_wrapper(
+ integration.get,
+ )
+
+ @cached_property
+ def wellknown(self) -> WellknownResourceWithStreamingResponse:
+ return WellknownResourceWithStreamingResponse(self._integration.wellknown)
+
+ @cached_property
+ def connect(self) -> ConnectResourceWithStreamingResponse:
+ return ConnectResourceWithStreamingResponse(self._integration.connect)
+
+ @cached_property
+ def oauth(self) -> OauthResourceWithStreamingResponse:
+ return OauthResourceWithStreamingResponse(self._integration.oauth)
+
+ @cached_property
+ def command(self) -> CommandResourceWithStreamingResponse:
+ return CommandResourceWithStreamingResponse(self._integration.command)
+
+
+class AsyncIntegrationResourceWithStreamingResponse:
+ def __init__(self, integration: AsyncIntegrationResource) -> None:
+ self._integration = integration
+
+ self.list = async_to_streamed_response_wrapper(
+ integration.list,
+ )
+ self.get = async_to_streamed_response_wrapper(
+ integration.get,
+ )
+
+ @cached_property
+ def wellknown(self) -> AsyncWellknownResourceWithStreamingResponse:
+ return AsyncWellknownResourceWithStreamingResponse(self._integration.wellknown)
+
+ @cached_property
+ def connect(self) -> AsyncConnectResourceWithStreamingResponse:
+ return AsyncConnectResourceWithStreamingResponse(self._integration.connect)
+
+ @cached_property
+ def oauth(self) -> AsyncOauthResourceWithStreamingResponse:
+ return AsyncOauthResourceWithStreamingResponse(self._integration.oauth)
+
+ @cached_property
+ def command(self) -> AsyncCommandResourceWithStreamingResponse:
+ return AsyncCommandResourceWithStreamingResponse(self._integration.command)
diff --git a/src/opencode/resources/integration/oauth.py b/src/opencode/resources/integration/oauth.py
new file mode 100644
index 0000000..cf7c090
--- /dev/null
+++ b/src/opencode/resources/integration/oauth.py
@@ -0,0 +1,518 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.integration import oauth_cancel_params, oauth_status_params, oauth_connect_params, oauth_complete_params
+from ...types.shared.form_answer import FormAnswer
+from ...types.shared.integration_id import IntegrationID
+from ...types.shared.integration_method_id import IntegrationMethodID
+from ...types.shared.integration_attempt_id import IntegrationAttemptID
+from ...types.integration.oauth_status_response import OauthStatusResponse
+from ...types.integration.oauth_connect_response import OauthConnectResponse
+
+__all__ = ["OauthResource", "AsyncOauthResource"]
+
+
+class OauthResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> OauthResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return OauthResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> OauthResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return OauthResourceWithStreamingResponse(self)
+
+ def cancel(
+ self,
+ attempt_id: IntegrationAttemptID,
+ *,
+ integration_id: IntegrationID,
+ location: Optional[oauth_cancel_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Cancel an OAuth attempt and release its resources.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ if not attempt_id:
+ raise ValueError(f"Expected a non-empty value for `attempt_id` but received {attempt_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template(
+ "/api/integration/{integration_id}/connect/oauth/{attempt_id}",
+ integration_id=integration_id,
+ attempt_id=attempt_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, oauth_cancel_params.OauthCancelParams),
+ ),
+ cast_to=NoneType,
+ )
+
+ def complete(
+ self,
+ attempt_id: IntegrationAttemptID,
+ *,
+ integration_id: IntegrationID,
+ code: Optional[str] | Omit = omit,
+ location: Optional[oauth_complete_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Complete a code-based OAuth attempt and store the resulting credential.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ if not attempt_id:
+ raise ValueError(f"Expected a non-empty value for `attempt_id` but received {attempt_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template(
+ "/api/integration/{integration_id}/connect/oauth/{attempt_id}/complete",
+ integration_id=integration_id,
+ attempt_id=attempt_id,
+ ),
+ body=maybe_transform({"code": code}, oauth_complete_params.OauthCompleteParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, oauth_complete_params.OauthCompleteParams),
+ ),
+ cast_to=NoneType,
+ )
+
+ def connect(
+ self,
+ integration_id: IntegrationID,
+ *,
+ method_id: IntegrationMethodID,
+ answer: Optional[FormAnswer] | Omit = omit,
+ label: Optional[str] | Omit = omit,
+ location: Optional[oauth_connect_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> OauthConnectResponse:
+ """
+ Start an OAuth attempt and return the authorization details.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ return self._post(
+ path_template("/api/integration/{integration_id}/connect/oauth", integration_id=integration_id),
+ body=maybe_transform(
+ {
+ "method_id": method_id,
+ "answer": answer,
+ "label": label,
+ },
+ oauth_connect_params.OauthConnectParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, oauth_connect_params.OauthConnectParams),
+ ),
+ cast_to=OauthConnectResponse,
+ )
+
+ def status(
+ self,
+ attempt_id: IntegrationAttemptID,
+ *,
+ integration_id: IntegrationID,
+ location: Optional[oauth_status_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> OauthStatusResponse:
+ """
+ Poll the current status of an OAuth attempt.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ if not attempt_id:
+ raise ValueError(f"Expected a non-empty value for `attempt_id` but received {attempt_id!r}")
+ return self._get(
+ path_template(
+ "/api/integration/{integration_id}/connect/oauth/{attempt_id}",
+ integration_id=integration_id,
+ attempt_id=attempt_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, oauth_status_params.OauthStatusParams),
+ ),
+ cast_to=OauthStatusResponse,
+ )
+
+
+class AsyncOauthResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncOauthResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncOauthResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncOauthResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncOauthResourceWithStreamingResponse(self)
+
+ async def cancel(
+ self,
+ attempt_id: IntegrationAttemptID,
+ *,
+ integration_id: IntegrationID,
+ location: Optional[oauth_cancel_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Cancel an OAuth attempt and release its resources.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ if not attempt_id:
+ raise ValueError(f"Expected a non-empty value for `attempt_id` but received {attempt_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template(
+ "/api/integration/{integration_id}/connect/oauth/{attempt_id}",
+ integration_id=integration_id,
+ attempt_id=attempt_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, oauth_cancel_params.OauthCancelParams),
+ ),
+ cast_to=NoneType,
+ )
+
+ async def complete(
+ self,
+ attempt_id: IntegrationAttemptID,
+ *,
+ integration_id: IntegrationID,
+ code: Optional[str] | Omit = omit,
+ location: Optional[oauth_complete_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Complete a code-based OAuth attempt and store the resulting credential.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ if not attempt_id:
+ raise ValueError(f"Expected a non-empty value for `attempt_id` but received {attempt_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template(
+ "/api/integration/{integration_id}/connect/oauth/{attempt_id}/complete",
+ integration_id=integration_id,
+ attempt_id=attempt_id,
+ ),
+ body=await async_maybe_transform({"code": code}, oauth_complete_params.OauthCompleteParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, oauth_complete_params.OauthCompleteParams),
+ ),
+ cast_to=NoneType,
+ )
+
+ async def connect(
+ self,
+ integration_id: IntegrationID,
+ *,
+ method_id: IntegrationMethodID,
+ answer: Optional[FormAnswer] | Omit = omit,
+ label: Optional[str] | Omit = omit,
+ location: Optional[oauth_connect_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> OauthConnectResponse:
+ """
+ Start an OAuth attempt and return the authorization details.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ return await self._post(
+ path_template("/api/integration/{integration_id}/connect/oauth", integration_id=integration_id),
+ body=await async_maybe_transform(
+ {
+ "method_id": method_id,
+ "answer": answer,
+ "label": label,
+ },
+ oauth_connect_params.OauthConnectParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, oauth_connect_params.OauthConnectParams),
+ ),
+ cast_to=OauthConnectResponse,
+ )
+
+ async def status(
+ self,
+ attempt_id: IntegrationAttemptID,
+ *,
+ integration_id: IntegrationID,
+ location: Optional[oauth_status_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> OauthStatusResponse:
+ """
+ Poll the current status of an OAuth attempt.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not integration_id:
+ raise ValueError(f"Expected a non-empty value for `integration_id` but received {integration_id!r}")
+ if not attempt_id:
+ raise ValueError(f"Expected a non-empty value for `attempt_id` but received {attempt_id!r}")
+ return await self._get(
+ path_template(
+ "/api/integration/{integration_id}/connect/oauth/{attempt_id}",
+ integration_id=integration_id,
+ attempt_id=attempt_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, oauth_status_params.OauthStatusParams),
+ ),
+ cast_to=OauthStatusResponse,
+ )
+
+
+class OauthResourceWithRawResponse:
+ def __init__(self, oauth: OauthResource) -> None:
+ self._oauth = oauth
+
+ self.cancel = to_raw_response_wrapper(
+ oauth.cancel,
+ )
+ self.complete = to_raw_response_wrapper(
+ oauth.complete,
+ )
+ self.connect = to_raw_response_wrapper(
+ oauth.connect,
+ )
+ self.status = to_raw_response_wrapper(
+ oauth.status,
+ )
+
+
+class AsyncOauthResourceWithRawResponse:
+ def __init__(self, oauth: AsyncOauthResource) -> None:
+ self._oauth = oauth
+
+ self.cancel = async_to_raw_response_wrapper(
+ oauth.cancel,
+ )
+ self.complete = async_to_raw_response_wrapper(
+ oauth.complete,
+ )
+ self.connect = async_to_raw_response_wrapper(
+ oauth.connect,
+ )
+ self.status = async_to_raw_response_wrapper(
+ oauth.status,
+ )
+
+
+class OauthResourceWithStreamingResponse:
+ def __init__(self, oauth: OauthResource) -> None:
+ self._oauth = oauth
+
+ self.cancel = to_streamed_response_wrapper(
+ oauth.cancel,
+ )
+ self.complete = to_streamed_response_wrapper(
+ oauth.complete,
+ )
+ self.connect = to_streamed_response_wrapper(
+ oauth.connect,
+ )
+ self.status = to_streamed_response_wrapper(
+ oauth.status,
+ )
+
+
+class AsyncOauthResourceWithStreamingResponse:
+ def __init__(self, oauth: AsyncOauthResource) -> None:
+ self._oauth = oauth
+
+ self.cancel = async_to_streamed_response_wrapper(
+ oauth.cancel,
+ )
+ self.complete = async_to_streamed_response_wrapper(
+ oauth.complete,
+ )
+ self.connect = async_to_streamed_response_wrapper(
+ oauth.connect,
+ )
+ self.status = async_to_streamed_response_wrapper(
+ oauth.status,
+ )
diff --git a/src/opencode/resources/integration/wellknown.py b/src/opencode/resources/integration/wellknown.py
new file mode 100644
index 0000000..201c02c
--- /dev/null
+++ b/src/opencode/resources/integration/wellknown.py
@@ -0,0 +1,176 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.integration import wellknown_add_params
+
+__all__ = ["WellknownResource", "AsyncWellknownResource"]
+
+
+class WellknownResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> WellknownResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return WellknownResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> WellknownResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return WellknownResourceWithStreamingResponse(self)
+
+ def add(
+ self,
+ *,
+ url: str,
+ location: Optional[wellknown_add_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Discover and persist an experimental wellknown integration source.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ "/api/experimental/integration/wellknown",
+ body=maybe_transform({"url": url}, wellknown_add_params.WellknownAddParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, wellknown_add_params.WellknownAddParams),
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncWellknownResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncWellknownResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncWellknownResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncWellknownResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncWellknownResourceWithStreamingResponse(self)
+
+ async def add(
+ self,
+ *,
+ url: str,
+ location: Optional[wellknown_add_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Discover and persist an experimental wellknown integration source.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ "/api/experimental/integration/wellknown",
+ body=await async_maybe_transform({"url": url}, wellknown_add_params.WellknownAddParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, wellknown_add_params.WellknownAddParams),
+ ),
+ cast_to=NoneType,
+ )
+
+
+class WellknownResourceWithRawResponse:
+ def __init__(self, wellknown: WellknownResource) -> None:
+ self._wellknown = wellknown
+
+ self.add = to_raw_response_wrapper(
+ wellknown.add,
+ )
+
+
+class AsyncWellknownResourceWithRawResponse:
+ def __init__(self, wellknown: AsyncWellknownResource) -> None:
+ self._wellknown = wellknown
+
+ self.add = async_to_raw_response_wrapper(
+ wellknown.add,
+ )
+
+
+class WellknownResourceWithStreamingResponse:
+ def __init__(self, wellknown: WellknownResource) -> None:
+ self._wellknown = wellknown
+
+ self.add = to_streamed_response_wrapper(
+ wellknown.add,
+ )
+
+
+class AsyncWellknownResourceWithStreamingResponse:
+ def __init__(self, wellknown: AsyncWellknownResource) -> None:
+ self._wellknown = wellknown
+
+ self.add = async_to_streamed_response_wrapper(
+ wellknown.add,
+ )
diff --git a/src/opencode/resources/location.py b/src/opencode/resources/location.py
index 9f819da..0c23fdf 100644
--- a/src/opencode/resources/location.py
+++ b/src/opencode/resources/location.py
@@ -1,231 +1,233 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``location`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
+from typing import Optional
+
+import httpx
-from .._base_client import RequestOptions
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import location_get_params
+from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- Location,
- LocationPublicInfo,
- )
+from .._base_client import make_request_options
+from ..types.shared.location_public_info import LocationPublicInfo
__all__ = ["LocationResource", "AsyncLocationResource"]
class LocationResource(SyncAPIResource):
- """Operations under ``client.location`` (OpenCode ``location``)."""
-
@cached_property
def with_raw_response(self) -> LocationResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return LocationResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> LocationResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return LocationResourceWithStreamingResponse(self)
def get(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[location_get_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> LocationPublicInfo:
- """Get location.
-
+ """
Resolve the requested location or the server default location.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/location``
- """
- return cast(
- "LocationPublicInfo",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/location",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="LocationPublicInfo",
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/location",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, location_get_params.LocationGetParams),
),
+ cast_to=LocationPublicInfo,
)
def reload(
self,
*,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Reload configuration.
-
- Shut down and rebuild every loaded location. Pending permissions and forms are cancelled; running sessions continue with fresh services at the next step boundary. Emits location.shutdown for client recovery and responds once all replacement builds settle.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ServiceUnavailableError: The server answered 503.
-
- ``POST /api/location/reload``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/location/reload",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
+ """
+ Shut down and rebuild every loaded location. Pending permissions and forms are
+ cancelled; running sessions continue with fresh services at the next step
+ boundary. Emits location.shutdown for client recovery and responds once all
+ replacement builds settle.
+ """
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ "/api/location/reload",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=NoneType,
)
-class LocationResourceWithRawResponse:
- """``client.location.with_raw_response``."""
-
- def __init__(self, resource: LocationResource) -> None:
- self._resource = resource
- self.get = to_raw_response_wrapper(resource.get)
- self.reload = to_raw_response_wrapper(resource.reload)
-
-
-class LocationResourceWithStreamingResponse:
- """``client.location.with_streaming_response``."""
-
- def __init__(self, resource: LocationResource) -> None:
- self._resource = resource
- self.get = to_streamed_response_wrapper(resource.get)
- self.reload = to_streamed_response_wrapper(resource.reload)
-
-
class AsyncLocationResource(AsyncAPIResource):
- """Operations under ``client.location`` (OpenCode ``location``)."""
-
@cached_property
def with_raw_response(self) -> AsyncLocationResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncLocationResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncLocationResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncLocationResourceWithStreamingResponse(self)
async def get(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[location_get_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> LocationPublicInfo:
- """Get location.
-
+ """
Resolve the requested location or the server default location.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/location``
- """
- return cast(
- "LocationPublicInfo",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/location",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="LocationPublicInfo",
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/location",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, location_get_params.LocationGetParams),
),
+ cast_to=LocationPublicInfo,
)
async def reload(
self,
*,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Reload configuration.
-
- Shut down and rebuild every loaded location. Pending permissions and forms are cancelled; running sessions continue with fresh services at the next step boundary. Emits location.shutdown for client recovery and responds once all replacement builds settle.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ServiceUnavailableError: The server answered 503.
-
- ``POST /api/location/reload``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/location/reload",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
+ """
+ Shut down and rebuild every loaded location. Pending permissions and forms are
+ cancelled; running sessions continue with fresh services at the next step
+ boundary. Emits location.shutdown for client recovery and responds once all
+ replacement builds settle.
+ """
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ "/api/location/reload",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=NoneType,
+ )
+
+
+class LocationResourceWithRawResponse:
+ def __init__(self, location: LocationResource) -> None:
+ self._location = location
+
+ self.get = to_raw_response_wrapper(
+ location.get,
+ )
+ self.reload = to_raw_response_wrapper(
+ location.reload,
)
class AsyncLocationResourceWithRawResponse:
- """``client.location.with_raw_response``."""
+ def __init__(self, location: AsyncLocationResource) -> None:
+ self._location = location
- def __init__(self, resource: AsyncLocationResource) -> None:
- self._resource = resource
- self.get = async_to_raw_response_wrapper(resource.get)
- self.reload = async_to_raw_response_wrapper(resource.reload)
+ self.get = async_to_raw_response_wrapper(
+ location.get,
+ )
+ self.reload = async_to_raw_response_wrapper(
+ location.reload,
+ )
+
+
+class LocationResourceWithStreamingResponse:
+ def __init__(self, location: LocationResource) -> None:
+ self._location = location
+
+ self.get = to_streamed_response_wrapper(
+ location.get,
+ )
+ self.reload = to_streamed_response_wrapper(
+ location.reload,
+ )
class AsyncLocationResourceWithStreamingResponse:
- """``client.location.with_streaming_response``."""
+ def __init__(self, location: AsyncLocationResource) -> None:
+ self._location = location
- def __init__(self, resource: AsyncLocationResource) -> None:
- self._resource = resource
- self.get = async_to_streamed_response_wrapper(resource.get)
- self.reload = async_to_streamed_response_wrapper(resource.reload)
+ self.get = async_to_streamed_response_wrapper(
+ location.get,
+ )
+ self.reload = async_to_streamed_response_wrapper(
+ location.reload,
+ )
diff --git a/src/opencode/resources/mcp/__init__.py b/src/opencode/resources/mcp/__init__.py
new file mode 100644
index 0000000..abd3e3e
--- /dev/null
+++ b/src/opencode/resources/mcp/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from .mcp import (
+ McpResource,
+ AsyncMcpResource,
+ McpResourceWithRawResponse,
+ AsyncMcpResourceWithRawResponse,
+ McpResourceWithStreamingResponse,
+ AsyncMcpResourceWithStreamingResponse,
+)
+from .resource import (
+ ResourceResource,
+ AsyncResourceResource,
+ ResourceResourceWithRawResponse,
+ AsyncResourceResourceWithRawResponse,
+ ResourceResourceWithStreamingResponse,
+ AsyncResourceResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "ResourceResource",
+ "AsyncResourceResource",
+ "ResourceResourceWithRawResponse",
+ "AsyncResourceResourceWithRawResponse",
+ "ResourceResourceWithStreamingResponse",
+ "AsyncResourceResourceWithStreamingResponse",
+ "McpResource",
+ "AsyncMcpResource",
+ "McpResourceWithRawResponse",
+ "AsyncMcpResourceWithRawResponse",
+ "McpResourceWithStreamingResponse",
+ "AsyncMcpResourceWithStreamingResponse",
+]
diff --git a/src/opencode/resources/mcp/mcp.py b/src/opencode/resources/mcp/mcp.py
new file mode 100644
index 0000000..1f9504d
--- /dev/null
+++ b/src/opencode/resources/mcp/mcp.py
@@ -0,0 +1,571 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ...types import mcp_add_params, mcp_list_params, mcp_remove_params, mcp_connect_params, mcp_disconnect_params
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from .resource import (
+ ResourceResource,
+ AsyncResourceResource,
+ ResourceResourceWithRawResponse,
+ AsyncResourceResourceWithRawResponse,
+ ResourceResourceWithStreamingResponse,
+ AsyncResourceResourceWithStreamingResponse,
+)
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.mcp_list_response import McpListResponse
+
+__all__ = ["McpResource", "AsyncMcpResource"]
+
+
+class McpResource(SyncAPIResource):
+ @cached_property
+ def resource(self) -> ResourceResource:
+ return ResourceResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> McpResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return McpResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> McpResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return McpResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ *,
+ location: Optional[mcp_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> McpListResponse:
+ """
+ Retrieve configured MCP servers and their connection status.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/mcp",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, mcp_list_params.McpListParams),
+ ),
+ cast_to=McpListResponse,
+ )
+
+ def add(
+ self,
+ server: str,
+ *,
+ config: mcp_add_params.Config,
+ location: Optional[mcp_add_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Add an MCP server at runtime or replace an existing one, connecting it
+ immediately.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not server:
+ raise ValueError(f"Expected a non-empty value for `server` but received {server!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._put(
+ path_template("/api/experimental/mcp/{server}", server=server),
+ body=maybe_transform({"config": config}, mcp_add_params.McpAddParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, mcp_add_params.McpAddParams),
+ ),
+ cast_to=NoneType,
+ )
+
+ def connect(
+ self,
+ server: str,
+ *,
+ location: Optional[mcp_connect_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Connect an MCP server at runtime, overriding a disabled configuration until
+ restart.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not server:
+ raise ValueError(f"Expected a non-empty value for `server` but received {server!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template("/api/experimental/mcp/{server}/connect", server=server),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, mcp_connect_params.McpConnectParams),
+ ),
+ cast_to=NoneType,
+ )
+
+ def disconnect(
+ self,
+ server: str,
+ *,
+ location: Optional[mcp_disconnect_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Disconnect an MCP server at runtime, removing its tools until reconnected.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not server:
+ raise ValueError(f"Expected a non-empty value for `server` but received {server!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template("/api/experimental/mcp/{server}/disconnect", server=server),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, mcp_disconnect_params.McpDisconnectParams),
+ ),
+ cast_to=NoneType,
+ )
+
+ def remove(
+ self,
+ server: str,
+ *,
+ location: Optional[mcp_remove_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Stop an MCP server and remove it from the runtime set until restart.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not server:
+ raise ValueError(f"Expected a non-empty value for `server` but received {server!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template("/api/experimental/mcp/{server}", server=server),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, mcp_remove_params.McpRemoveParams),
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncMcpResource(AsyncAPIResource):
+ @cached_property
+ def resource(self) -> AsyncResourceResource:
+ return AsyncResourceResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncMcpResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncMcpResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncMcpResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncMcpResourceWithStreamingResponse(self)
+
+ async def list(
+ self,
+ *,
+ location: Optional[mcp_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> McpListResponse:
+ """
+ Retrieve configured MCP servers and their connection status.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/mcp",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, mcp_list_params.McpListParams),
+ ),
+ cast_to=McpListResponse,
+ )
+
+ async def add(
+ self,
+ server: str,
+ *,
+ config: mcp_add_params.Config,
+ location: Optional[mcp_add_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Add an MCP server at runtime or replace an existing one, connecting it
+ immediately.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not server:
+ raise ValueError(f"Expected a non-empty value for `server` but received {server!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._put(
+ path_template("/api/experimental/mcp/{server}", server=server),
+ body=await async_maybe_transform({"config": config}, mcp_add_params.McpAddParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, mcp_add_params.McpAddParams),
+ ),
+ cast_to=NoneType,
+ )
+
+ async def connect(
+ self,
+ server: str,
+ *,
+ location: Optional[mcp_connect_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Connect an MCP server at runtime, overriding a disabled configuration until
+ restart.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not server:
+ raise ValueError(f"Expected a non-empty value for `server` but received {server!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template("/api/experimental/mcp/{server}/connect", server=server),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, mcp_connect_params.McpConnectParams),
+ ),
+ cast_to=NoneType,
+ )
+
+ async def disconnect(
+ self,
+ server: str,
+ *,
+ location: Optional[mcp_disconnect_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Disconnect an MCP server at runtime, removing its tools until reconnected.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not server:
+ raise ValueError(f"Expected a non-empty value for `server` but received {server!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template("/api/experimental/mcp/{server}/disconnect", server=server),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, mcp_disconnect_params.McpDisconnectParams),
+ ),
+ cast_to=NoneType,
+ )
+
+ async def remove(
+ self,
+ server: str,
+ *,
+ location: Optional[mcp_remove_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Stop an MCP server and remove it from the runtime set until restart.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not server:
+ raise ValueError(f"Expected a non-empty value for `server` but received {server!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template("/api/experimental/mcp/{server}", server=server),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, mcp_remove_params.McpRemoveParams),
+ ),
+ cast_to=NoneType,
+ )
+
+
+class McpResourceWithRawResponse:
+ def __init__(self, mcp: McpResource) -> None:
+ self._mcp = mcp
+
+ self.list = to_raw_response_wrapper(
+ mcp.list,
+ )
+ self.add = to_raw_response_wrapper(
+ mcp.add,
+ )
+ self.connect = to_raw_response_wrapper(
+ mcp.connect,
+ )
+ self.disconnect = to_raw_response_wrapper(
+ mcp.disconnect,
+ )
+ self.remove = to_raw_response_wrapper(
+ mcp.remove,
+ )
+
+ @cached_property
+ def resource(self) -> ResourceResourceWithRawResponse:
+ return ResourceResourceWithRawResponse(self._mcp.resource)
+
+
+class AsyncMcpResourceWithRawResponse:
+ def __init__(self, mcp: AsyncMcpResource) -> None:
+ self._mcp = mcp
+
+ self.list = async_to_raw_response_wrapper(
+ mcp.list,
+ )
+ self.add = async_to_raw_response_wrapper(
+ mcp.add,
+ )
+ self.connect = async_to_raw_response_wrapper(
+ mcp.connect,
+ )
+ self.disconnect = async_to_raw_response_wrapper(
+ mcp.disconnect,
+ )
+ self.remove = async_to_raw_response_wrapper(
+ mcp.remove,
+ )
+
+ @cached_property
+ def resource(self) -> AsyncResourceResourceWithRawResponse:
+ return AsyncResourceResourceWithRawResponse(self._mcp.resource)
+
+
+class McpResourceWithStreamingResponse:
+ def __init__(self, mcp: McpResource) -> None:
+ self._mcp = mcp
+
+ self.list = to_streamed_response_wrapper(
+ mcp.list,
+ )
+ self.add = to_streamed_response_wrapper(
+ mcp.add,
+ )
+ self.connect = to_streamed_response_wrapper(
+ mcp.connect,
+ )
+ self.disconnect = to_streamed_response_wrapper(
+ mcp.disconnect,
+ )
+ self.remove = to_streamed_response_wrapper(
+ mcp.remove,
+ )
+
+ @cached_property
+ def resource(self) -> ResourceResourceWithStreamingResponse:
+ return ResourceResourceWithStreamingResponse(self._mcp.resource)
+
+
+class AsyncMcpResourceWithStreamingResponse:
+ def __init__(self, mcp: AsyncMcpResource) -> None:
+ self._mcp = mcp
+
+ self.list = async_to_streamed_response_wrapper(
+ mcp.list,
+ )
+ self.add = async_to_streamed_response_wrapper(
+ mcp.add,
+ )
+ self.connect = async_to_streamed_response_wrapper(
+ mcp.connect,
+ )
+ self.disconnect = async_to_streamed_response_wrapper(
+ mcp.disconnect,
+ )
+ self.remove = async_to_streamed_response_wrapper(
+ mcp.remove,
+ )
+
+ @cached_property
+ def resource(self) -> AsyncResourceResourceWithStreamingResponse:
+ return AsyncResourceResourceWithStreamingResponse(self._mcp.resource)
diff --git a/src/opencode/resources/mcp/resource.py b/src/opencode/resources/mcp/resource.py
new file mode 100644
index 0000000..5643164
--- /dev/null
+++ b/src/opencode/resources/mcp/resource.py
@@ -0,0 +1,173 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...types.mcp import resource_catalog_params
+from ..._base_client import make_request_options
+from ...types.mcp.resource_catalog_response import ResourceCatalogResponse
+
+__all__ = ["ResourceResource", "AsyncResourceResource"]
+
+
+class ResourceResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> ResourceResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return ResourceResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ResourceResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return ResourceResourceWithStreamingResponse(self)
+
+ def catalog(
+ self,
+ *,
+ location: Optional[resource_catalog_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ResourceCatalogResponse:
+ """
+ Retrieve resources and resource templates from connected MCP servers.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/mcp/resource",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, resource_catalog_params.ResourceCatalogParams),
+ ),
+ cast_to=ResourceCatalogResponse,
+ )
+
+
+class AsyncResourceResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncResourceResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncResourceResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncResourceResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncResourceResourceWithStreamingResponse(self)
+
+ async def catalog(
+ self,
+ *,
+ location: Optional[resource_catalog_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ResourceCatalogResponse:
+ """
+ Retrieve resources and resource templates from connected MCP servers.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/mcp/resource",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"location": location}, resource_catalog_params.ResourceCatalogParams
+ ),
+ ),
+ cast_to=ResourceCatalogResponse,
+ )
+
+
+class ResourceResourceWithRawResponse:
+ def __init__(self, resource: ResourceResource) -> None:
+ self._resource = resource
+
+ self.catalog = to_raw_response_wrapper(
+ resource.catalog,
+ )
+
+
+class AsyncResourceResourceWithRawResponse:
+ def __init__(self, resource: AsyncResourceResource) -> None:
+ self._resource = resource
+
+ self.catalog = async_to_raw_response_wrapper(
+ resource.catalog,
+ )
+
+
+class ResourceResourceWithStreamingResponse:
+ def __init__(self, resource: ResourceResource) -> None:
+ self._resource = resource
+
+ self.catalog = to_streamed_response_wrapper(
+ resource.catalog,
+ )
+
+
+class AsyncResourceResourceWithStreamingResponse:
+ def __init__(self, resource: AsyncResourceResource) -> None:
+ self._resource = resource
+
+ self.catalog = async_to_streamed_response_wrapper(
+ resource.catalog,
+ )
diff --git a/src/opencode/resources/message.py b/src/opencode/resources/message.py
index 9972f88..7402b86 100644
--- a/src/opencode/resources/message.py
+++ b/src/opencode/resources/message.py
@@ -1,55 +1,59 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``message`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from functools import cached_property
-from typing import TYPE_CHECKING, Literal
+from typing import Optional
+from typing_extensions import Literal
+
+import httpx
-from .._base_client import RequestOptions
-from .._pagination import AsyncPaginator, SyncCursorPage
-from .._qs import encode_path_segment
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import message_list_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import path_template, maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, OpenEnum, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- SessionID,
- SessionMessageInfo,
- )
+from ..pagination import SyncCursorPage, AsyncCursorPage
+from .._base_client import AsyncPaginator, make_request_options
+from ..types.shared.session_id import SessionID
+from ..types.shared.session_message_info import SessionMessageInfo
__all__ = ["MessageResource", "AsyncMessageResource"]
class MessageResource(SyncAPIResource):
- """Operations under ``client.message`` (OpenCode ``message``)."""
-
@cached_property
def with_raw_response(self) -> MessageResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return MessageResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> MessageResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return MessageResourceWithStreamingResponse(self)
def list(
self,
session_id: SessionID,
*,
- limit: int | NotGiven | None = NOT_GIVEN,
- order: OpenEnum[Literal["asc", "desc"]] | NotGiven | None = NOT_GIVEN,
- cursor: str | NotGiven | None = NOT_GIVEN,
- type: OpenEnum[
+ cursor: Optional[str] | Omit = omit,
+ limit: Optional[int] | Omit = omit,
+ order: Optional[Literal["asc", "desc"]] | Omit = omit,
+ type: Optional[
Literal[
"agent-switched",
"model-switched",
@@ -64,83 +68,89 @@ def list(
"idle",
]
]
- | NotGiven
- | None = NOT_GIVEN,
+ | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> SyncCursorPage[SessionMessageInfo]:
- """Get session messages.
-
- Retrieve projected messages for a session, optionally filtered by type. Items keep the requested order across pages; use cursor.next or cursor.previous to move through the ordered timeline, passing the same type filter on each page.
+ """
+ Retrieve projected messages for a session, optionally filtered by type. Items
+ keep the requested order across pages; use cursor.next or cursor.previous to
+ move through the ordered timeline, passing the same type filter on each page.
Args:
- limit: Maximum number of messages to return. When omitted, the endpoint returns its default page size.
- order: Message order for the first page. Use desc for newest first or asc for oldest first.
- cursor: Opaque pagination cursor returned as cursor.previous or cursor.next in the previous response. Do not combine with order.
- type: Filter by message type before pagination. When omitted, all message types are returned. Pass the same type when following cursors.
-
- Raises:
- InvalidCursorError: The server answered 400.
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- UnknownError: The server answered 500.
-
- ``GET /api/session/:sessionID/message``
+ limit: Maximum number of messages to return. When omitted, the endpoint returns its
+ default page size.
+
+ order: Message order for the first page. Use desc for newest first or asc for oldest
+ first.
+
+ type: Filter by message type before pagination. When omitted, all message types are
+ returned. Pass the same type when following cursors.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return self._client.page(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}/message",
- query={"limit": limit, "order": order, "cursor": cursor, "type": type},
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._get_api_list(
+ path_template("/api/session/{session_id}/message", session_id=session_id),
+ page=SyncCursorPage[SessionMessageInfo],
+ options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
+ query=maybe_transform(
+ {
+ "cursor": cursor,
+ "limit": limit,
+ "order": order,
+ "type": type,
+ },
+ message_list_params.MessageListParams,
+ ),
),
- response_type="SessionMessagesResponse",
+ model=SessionMessageInfo,
)
-class MessageResourceWithRawResponse:
- """``client.message.with_raw_response``."""
-
- def __init__(self, resource: MessageResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
-
-
-class MessageResourceWithStreamingResponse:
- """``client.message.with_streaming_response``."""
-
- def __init__(self, resource: MessageResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
-
-
class AsyncMessageResource(AsyncAPIResource):
- """Operations under ``client.message`` (OpenCode ``message``)."""
-
@cached_property
def with_raw_response(self) -> AsyncMessageResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncMessageResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncMessageResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncMessageResourceWithStreamingResponse(self)
def list(
self,
session_id: SessionID,
*,
- limit: int | NotGiven | None = NOT_GIVEN,
- order: OpenEnum[Literal["asc", "desc"]] | NotGiven | None = NOT_GIVEN,
- cursor: str | NotGiven | None = NOT_GIVEN,
- type: OpenEnum[
+ cursor: Optional[str] | Omit = omit,
+ limit: Optional[int] | Omit = omit,
+ order: Optional[Literal["asc", "desc"]] | Omit = omit,
+ type: Optional[
Literal[
"agent-switched",
"model-switched",
@@ -155,57 +165,92 @@ def list(
"idle",
]
]
- | NotGiven
- | None = NOT_GIVEN,
+ | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> AsyncPaginator[SessionMessageInfo]:
- """Get session messages.
-
- Retrieve projected messages for a session, optionally filtered by type. Items keep the requested order across pages; use cursor.next or cursor.previous to move through the ordered timeline, passing the same type filter on each page.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncPaginator[SessionMessageInfo, AsyncCursorPage[SessionMessageInfo]]:
+ """
+ Retrieve projected messages for a session, optionally filtered by type. Items
+ keep the requested order across pages; use cursor.next or cursor.previous to
+ move through the ordered timeline, passing the same type filter on each page.
Args:
- limit: Maximum number of messages to return. When omitted, the endpoint returns its default page size.
- order: Message order for the first page. Use desc for newest first or asc for oldest first.
- cursor: Opaque pagination cursor returned as cursor.previous or cursor.next in the previous response. Do not combine with order.
- type: Filter by message type before pagination. When omitted, all message types are returned. Pass the same type when following cursors.
-
- Raises:
- InvalidCursorError: The server answered 400.
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- UnknownError: The server answered 500.
-
- ``GET /api/session/:sessionID/message``
+ limit: Maximum number of messages to return. When omitted, the endpoint returns its
+ default page size.
+
+ order: Message order for the first page. Use desc for newest first or asc for oldest
+ first.
+
+ type: Filter by message type before pagination. When omitted, all message types are
+ returned. Pass the same type when following cursors.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return self._client.page(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}/message",
- query={"limit": limit, "order": order, "cursor": cursor, "type": type},
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._get_api_list(
+ path_template("/api/session/{session_id}/message", session_id=session_id),
+ page=AsyncCursorPage[SessionMessageInfo],
+ options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
+ query=maybe_transform(
+ {
+ "cursor": cursor,
+ "limit": limit,
+ "order": order,
+ "type": type,
+ },
+ message_list_params.MessageListParams,
+ ),
),
- response_type="SessionMessagesResponse",
+ model=SessionMessageInfo,
+ )
+
+
+class MessageResourceWithRawResponse:
+ def __init__(self, message: MessageResource) -> None:
+ self._message = message
+
+ self.list = to_raw_response_wrapper(
+ message.list,
)
class AsyncMessageResourceWithRawResponse:
- """``client.message.with_raw_response``."""
+ def __init__(self, message: AsyncMessageResource) -> None:
+ self._message = message
+
+ self.list = async_to_raw_response_wrapper(
+ message.list,
+ )
- def __init__(self, resource: AsyncMessageResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
+
+class MessageResourceWithStreamingResponse:
+ def __init__(self, message: MessageResource) -> None:
+ self._message = message
+
+ self.list = to_streamed_response_wrapper(
+ message.list,
+ )
class AsyncMessageResourceWithStreamingResponse:
- """``client.message.with_streaming_response``."""
+ def __init__(self, message: AsyncMessageResource) -> None:
+ self._message = message
- def __init__(self, resource: AsyncMessageResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
+ self.list = async_to_streamed_response_wrapper(
+ message.list,
+ )
diff --git a/src/opencode/resources/migration/__init__.py b/src/opencode/resources/migration/__init__.py
new file mode 100644
index 0000000..5470629
--- /dev/null
+++ b/src/opencode/resources/migration/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from .v1 import (
+ V1Resource,
+ AsyncV1Resource,
+ V1ResourceWithRawResponse,
+ AsyncV1ResourceWithRawResponse,
+ V1ResourceWithStreamingResponse,
+ AsyncV1ResourceWithStreamingResponse,
+)
+from .migration import (
+ MigrationResource,
+ AsyncMigrationResource,
+ MigrationResourceWithRawResponse,
+ AsyncMigrationResourceWithRawResponse,
+ MigrationResourceWithStreamingResponse,
+ AsyncMigrationResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "V1Resource",
+ "AsyncV1Resource",
+ "V1ResourceWithRawResponse",
+ "AsyncV1ResourceWithRawResponse",
+ "V1ResourceWithStreamingResponse",
+ "AsyncV1ResourceWithStreamingResponse",
+ "MigrationResource",
+ "AsyncMigrationResource",
+ "MigrationResourceWithRawResponse",
+ "AsyncMigrationResourceWithRawResponse",
+ "MigrationResourceWithStreamingResponse",
+ "AsyncMigrationResourceWithStreamingResponse",
+]
diff --git a/src/opencode/resources/migration/migration.py b/src/opencode/resources/migration/migration.py
new file mode 100644
index 0000000..06b9074
--- /dev/null
+++ b/src/opencode/resources/migration/migration.py
@@ -0,0 +1,105 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from .v1 import (
+ V1Resource,
+ AsyncV1Resource,
+ V1ResourceWithRawResponse,
+ AsyncV1ResourceWithRawResponse,
+ V1ResourceWithStreamingResponse,
+ AsyncV1ResourceWithStreamingResponse,
+)
+from ..._types import Body, Query, Headers, NotGiven, not_given
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+
+__all__ = ["MigrationResource", "AsyncMigrationResource"]
+
+
+class MigrationResource(SyncAPIResource):
+ @cached_property
+ def v1(self) -> V1Resource:
+ return V1Resource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> MigrationResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return MigrationResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> MigrationResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return MigrationResourceWithStreamingResponse(self)
+
+
+class AsyncMigrationResource(AsyncAPIResource):
+ @cached_property
+ def v1(self) -> AsyncV1Resource:
+ return AsyncV1Resource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncMigrationResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncMigrationResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncMigrationResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncMigrationResourceWithStreamingResponse(self)
+
+
+class MigrationResourceWithRawResponse:
+ def __init__(self, migration: MigrationResource) -> None:
+ self._migration = migration
+
+ @cached_property
+ def v1(self) -> V1ResourceWithRawResponse:
+ return V1ResourceWithRawResponse(self._migration.v1)
+
+
+class AsyncMigrationResourceWithRawResponse:
+ def __init__(self, migration: AsyncMigrationResource) -> None:
+ self._migration = migration
+
+ @cached_property
+ def v1(self) -> AsyncV1ResourceWithRawResponse:
+ return AsyncV1ResourceWithRawResponse(self._migration.v1)
+
+
+class MigrationResourceWithStreamingResponse:
+ def __init__(self, migration: MigrationResource) -> None:
+ self._migration = migration
+
+ @cached_property
+ def v1(self) -> V1ResourceWithStreamingResponse:
+ return V1ResourceWithStreamingResponse(self._migration.v1)
+
+
+class AsyncMigrationResourceWithStreamingResponse:
+ def __init__(self, migration: AsyncMigrationResource) -> None:
+ self._migration = migration
+
+ @cached_property
+ def v1(self) -> AsyncV1ResourceWithStreamingResponse:
+ return AsyncV1ResourceWithStreamingResponse(self._migration.v1)
diff --git a/src/opencode/resources/migration/v1.py b/src/opencode/resources/migration/v1.py
new file mode 100644
index 0000000..37f22df
--- /dev/null
+++ b/src/opencode/resources/migration/v1.py
@@ -0,0 +1,135 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import Body, Query, Headers, NotGiven, not_given
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.migration.v1_status_response import V1StatusResponse
+
+__all__ = ["V1Resource", "AsyncV1Resource"]
+
+
+class V1Resource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> V1ResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return V1ResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> V1ResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return V1ResourceWithStreamingResponse(self)
+
+ def status(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> V1StatusResponse:
+ """Return the progress of the V1 to V2 session history migration."""
+ return self._get(
+ "/api/experimental/migration/v1",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=V1StatusResponse,
+ )
+
+
+class AsyncV1Resource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncV1ResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncV1ResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncV1ResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncV1ResourceWithStreamingResponse(self)
+
+ async def status(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> V1StatusResponse:
+ """Return the progress of the V1 to V2 session history migration."""
+ return await self._get(
+ "/api/experimental/migration/v1",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=V1StatusResponse,
+ )
+
+
+class V1ResourceWithRawResponse:
+ def __init__(self, v1: V1Resource) -> None:
+ self._v1 = v1
+
+ self.status = to_raw_response_wrapper(
+ v1.status,
+ )
+
+
+class AsyncV1ResourceWithRawResponse:
+ def __init__(self, v1: AsyncV1Resource) -> None:
+ self._v1 = v1
+
+ self.status = async_to_raw_response_wrapper(
+ v1.status,
+ )
+
+
+class V1ResourceWithStreamingResponse:
+ def __init__(self, v1: V1Resource) -> None:
+ self._v1 = v1
+
+ self.status = to_streamed_response_wrapper(
+ v1.status,
+ )
+
+
+class AsyncV1ResourceWithStreamingResponse:
+ def __init__(self, v1: AsyncV1Resource) -> None:
+ self._v1 = v1
+
+ self.status = async_to_streamed_response_wrapper(
+ v1.status,
+ )
diff --git a/src/opencode/resources/model.py b/src/opencode/resources/model.py
index 0e6656a..90f9207 100644
--- a/src/opencode/resources/model.py
+++ b/src/opencode/resources/model.py
@@ -1,240 +1,256 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``model`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
+from typing import Optional
+
+import httpx
-from .._base_client import RequestOptions
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import model_list_params, model_default_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- Location,
- ModelDefaultResponse,
- ModelListResponse,
- )
+from .._base_client import make_request_options
+from ..types.model_list_response import ModelListResponse
+from ..types.model_default_response import ModelDefaultResponse
__all__ = ["ModelResource", "AsyncModelResource"]
class ModelResource(SyncAPIResource):
- """Operations under ``client.model`` (OpenCode ``model``)."""
-
@cached_property
def with_raw_response(self) -> ModelResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return ModelResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> ModelResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return ModelResourceWithStreamingResponse(self)
def list(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[model_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ModelListResponse:
- """List models.
-
- Retrieve the current snapshot of available models ordered by release date. The snapshot may precede initial plugin settlement.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/model``
- """
- return cast(
- "ModelListResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/model",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ModelListResponse",
+ """
+ Retrieve the current snapshot of available models ordered by release date. The
+ snapshot may precede initial plugin settlement.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/model",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, model_list_params.ModelListParams),
),
+ cast_to=ModelListResponse,
)
def default(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[model_default_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ModelDefaultResponse:
- """Get default model.
-
+ """
Retrieve the model used when a session has no explicit model selection.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/model/default``
- """
- return cast(
- "ModelDefaultResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/model/default",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ModelDefaultResponse",
- ),
- )
+ Args:
+ extra_headers: Send extra headers
+ extra_query: Add additional query parameters to the request
-class ModelResourceWithRawResponse:
- """``client.model.with_raw_response``."""
-
- def __init__(self, resource: ModelResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
- self.default = to_raw_response_wrapper(resource.default)
+ extra_body: Add additional JSON properties to the request
-
-class ModelResourceWithStreamingResponse:
- """``client.model.with_streaming_response``."""
-
- def __init__(self, resource: ModelResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
- self.default = to_streamed_response_wrapper(resource.default)
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/model/default",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, model_default_params.ModelDefaultParams),
+ ),
+ cast_to=ModelDefaultResponse,
+ )
class AsyncModelResource(AsyncAPIResource):
- """Operations under ``client.model`` (OpenCode ``model``)."""
-
@cached_property
def with_raw_response(self) -> AsyncModelResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncModelResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncModelResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncModelResourceWithStreamingResponse(self)
async def list(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[model_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ModelListResponse:
- """List models.
-
- Retrieve the current snapshot of available models ordered by release date. The snapshot may precede initial plugin settlement.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/model``
- """
- return cast(
- "ModelListResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/model",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ModelListResponse",
+ """
+ Retrieve the current snapshot of available models ordered by release date. The
+ snapshot may precede initial plugin settlement.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/model",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, model_list_params.ModelListParams),
),
+ cast_to=ModelListResponse,
)
async def default(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[model_default_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ModelDefaultResponse:
- """Get default model.
-
+ """
Retrieve the model used when a session has no explicit model selection.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/model/default``
- """
- return cast(
- "ModelDefaultResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/model/default",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ModelDefaultResponse",
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/model/default",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, model_default_params.ModelDefaultParams),
),
+ cast_to=ModelDefaultResponse,
+ )
+
+
+class ModelResourceWithRawResponse:
+ def __init__(self, model: ModelResource) -> None:
+ self._model = model
+
+ self.list = to_raw_response_wrapper(
+ model.list,
+ )
+ self.default = to_raw_response_wrapper(
+ model.default,
)
class AsyncModelResourceWithRawResponse:
- """``client.model.with_raw_response``."""
+ def __init__(self, model: AsyncModelResource) -> None:
+ self._model = model
+
+ self.list = async_to_raw_response_wrapper(
+ model.list,
+ )
+ self.default = async_to_raw_response_wrapper(
+ model.default,
+ )
+
+
+class ModelResourceWithStreamingResponse:
+ def __init__(self, model: ModelResource) -> None:
+ self._model = model
- def __init__(self, resource: AsyncModelResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
- self.default = async_to_raw_response_wrapper(resource.default)
+ self.list = to_streamed_response_wrapper(
+ model.list,
+ )
+ self.default = to_streamed_response_wrapper(
+ model.default,
+ )
class AsyncModelResourceWithStreamingResponse:
- """``client.model.with_streaming_response``."""
+ def __init__(self, model: AsyncModelResource) -> None:
+ self._model = model
- def __init__(self, resource: AsyncModelResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.default = async_to_streamed_response_wrapper(resource.default)
+ self.list = async_to_streamed_response_wrapper(
+ model.list,
+ )
+ self.default = async_to_streamed_response_wrapper(
+ model.default,
+ )
diff --git a/src/opencode/resources/permission/__init__.py b/src/opencode/resources/permission/__init__.py
new file mode 100644
index 0000000..ea3703b
--- /dev/null
+++ b/src/opencode/resources/permission/__init__.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from .saved import (
+ SavedResource,
+ AsyncSavedResource,
+ SavedResourceWithRawResponse,
+ AsyncSavedResourceWithRawResponse,
+ SavedResourceWithStreamingResponse,
+ AsyncSavedResourceWithStreamingResponse,
+)
+from .request import (
+ RequestResource,
+ AsyncRequestResource,
+ RequestResourceWithRawResponse,
+ AsyncRequestResourceWithRawResponse,
+ RequestResourceWithStreamingResponse,
+ AsyncRequestResourceWithStreamingResponse,
+)
+from .permission import (
+ PermissionResource,
+ AsyncPermissionResource,
+ PermissionResourceWithRawResponse,
+ AsyncPermissionResourceWithRawResponse,
+ PermissionResourceWithStreamingResponse,
+ AsyncPermissionResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "RequestResource",
+ "AsyncRequestResource",
+ "RequestResourceWithRawResponse",
+ "AsyncRequestResourceWithRawResponse",
+ "RequestResourceWithStreamingResponse",
+ "AsyncRequestResourceWithStreamingResponse",
+ "SavedResource",
+ "AsyncSavedResource",
+ "SavedResourceWithRawResponse",
+ "AsyncSavedResourceWithRawResponse",
+ "SavedResourceWithStreamingResponse",
+ "AsyncSavedResourceWithStreamingResponse",
+ "PermissionResource",
+ "AsyncPermissionResource",
+ "PermissionResourceWithRawResponse",
+ "AsyncPermissionResourceWithRawResponse",
+ "PermissionResourceWithStreamingResponse",
+ "AsyncPermissionResourceWithStreamingResponse",
+]
diff --git a/src/opencode/resources/permission/permission.py b/src/opencode/resources/permission/permission.py
new file mode 100644
index 0000000..ddfb701
--- /dev/null
+++ b/src/opencode/resources/permission/permission.py
@@ -0,0 +1,552 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Optional
+
+import httpx
+
+from .saved import (
+ SavedResource,
+ AsyncSavedResource,
+ SavedResourceWithRawResponse,
+ AsyncSavedResourceWithRawResponse,
+ SavedResourceWithStreamingResponse,
+ AsyncSavedResourceWithStreamingResponse,
+)
+from ...types import permission_reply_params, permission_create_params
+from .request import (
+ RequestResource,
+ AsyncRequestResource,
+ RequestResourceWithRawResponse,
+ AsyncRequestResourceWithRawResponse,
+ RequestResourceWithStreamingResponse,
+ AsyncRequestResourceWithStreamingResponse,
+)
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.shared.agent_id import AgentID
+from ...types.shared.session_id import SessionID
+from ...types.shared.permission_id import PermissionID
+from ...types.permission_get_response import PermissionGetResponse
+from ...types.shared.permission_reply import PermissionReply
+from ...types.permission_list_response import PermissionListResponse
+from ...types.shared.permission_source import PermissionSource
+from ...types.permission_create_response import PermissionCreateResponse
+
+__all__ = ["PermissionResource", "AsyncPermissionResource"]
+
+
+class PermissionResource(SyncAPIResource):
+ @cached_property
+ def request(self) -> RequestResource:
+ return RequestResource(self._client)
+
+ @cached_property
+ def saved(self) -> SavedResource:
+ return SavedResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> PermissionResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return PermissionResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> PermissionResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return PermissionResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ session_id: SessionID,
+ *,
+ action: str,
+ resources: SequenceNotStr[str],
+ id: Optional[PermissionID] | Omit = omit,
+ agent: Optional[AgentID] | Omit = omit,
+ metadata: Dict[str, object] | Omit = omit,
+ save: SequenceNotStr[str] | Omit = omit,
+ source: PermissionSource | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PermissionCreateResponse:
+ """
+ Evaluate and, when approval is required, create a permission request for a
+ session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._post(
+ path_template("/api/session/{session_id}/permission", session_id=session_id),
+ body=maybe_transform(
+ {
+ "action": action,
+ "resources": resources,
+ "id": id,
+ "agent": agent,
+ "metadata": metadata,
+ "save": save,
+ "source": source,
+ },
+ permission_create_params.PermissionCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PermissionCreateResponse,
+ )
+
+ def list(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PermissionListResponse:
+ """
+ Retrieve pending permission requests owned by a session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._get(
+ path_template("/api/session/{session_id}/permission", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PermissionListResponse,
+ )
+
+ def get(
+ self,
+ request_id: PermissionID,
+ *,
+ session_id: SessionID,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PermissionGetResponse:
+ """
+ Retrieve a pending permission request owned by a session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not request_id:
+ raise ValueError(f"Expected a non-empty value for `request_id` but received {request_id!r}")
+ return self._get(
+ path_template(
+ "/api/session/{session_id}/permission/{request_id}", session_id=session_id, request_id=request_id
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PermissionGetResponse,
+ )
+
+ def reply(
+ self,
+ request_id: PermissionID,
+ *,
+ session_id: SessionID,
+ decision: PermissionReply,
+ message: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Respond to a pending permission request owned by a session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not request_id:
+ raise ValueError(f"Expected a non-empty value for `request_id` but received {request_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template(
+ "/api/session/{session_id}/permission/{request_id}/reply", session_id=session_id, request_id=request_id
+ ),
+ body=maybe_transform(
+ {
+ "decision": decision,
+ "message": message,
+ },
+ permission_reply_params.PermissionReplyParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncPermissionResource(AsyncAPIResource):
+ @cached_property
+ def request(self) -> AsyncRequestResource:
+ return AsyncRequestResource(self._client)
+
+ @cached_property
+ def saved(self) -> AsyncSavedResource:
+ return AsyncSavedResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncPermissionResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncPermissionResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncPermissionResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncPermissionResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ session_id: SessionID,
+ *,
+ action: str,
+ resources: SequenceNotStr[str],
+ id: Optional[PermissionID] | Omit = omit,
+ agent: Optional[AgentID] | Omit = omit,
+ metadata: Dict[str, object] | Omit = omit,
+ save: SequenceNotStr[str] | Omit = omit,
+ source: PermissionSource | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PermissionCreateResponse:
+ """
+ Evaluate and, when approval is required, create a permission request for a
+ session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._post(
+ path_template("/api/session/{session_id}/permission", session_id=session_id),
+ body=await async_maybe_transform(
+ {
+ "action": action,
+ "resources": resources,
+ "id": id,
+ "agent": agent,
+ "metadata": metadata,
+ "save": save,
+ "source": source,
+ },
+ permission_create_params.PermissionCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PermissionCreateResponse,
+ )
+
+ async def list(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PermissionListResponse:
+ """
+ Retrieve pending permission requests owned by a session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._get(
+ path_template("/api/session/{session_id}/permission", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PermissionListResponse,
+ )
+
+ async def get(
+ self,
+ request_id: PermissionID,
+ *,
+ session_id: SessionID,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PermissionGetResponse:
+ """
+ Retrieve a pending permission request owned by a session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not request_id:
+ raise ValueError(f"Expected a non-empty value for `request_id` but received {request_id!r}")
+ return await self._get(
+ path_template(
+ "/api/session/{session_id}/permission/{request_id}", session_id=session_id, request_id=request_id
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PermissionGetResponse,
+ )
+
+ async def reply(
+ self,
+ request_id: PermissionID,
+ *,
+ session_id: SessionID,
+ decision: PermissionReply,
+ message: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Respond to a pending permission request owned by a session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not request_id:
+ raise ValueError(f"Expected a non-empty value for `request_id` but received {request_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template(
+ "/api/session/{session_id}/permission/{request_id}/reply", session_id=session_id, request_id=request_id
+ ),
+ body=await async_maybe_transform(
+ {
+ "decision": decision,
+ "message": message,
+ },
+ permission_reply_params.PermissionReplyParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class PermissionResourceWithRawResponse:
+ def __init__(self, permission: PermissionResource) -> None:
+ self._permission = permission
+
+ self.create = to_raw_response_wrapper(
+ permission.create,
+ )
+ self.list = to_raw_response_wrapper(
+ permission.list,
+ )
+ self.get = to_raw_response_wrapper(
+ permission.get,
+ )
+ self.reply = to_raw_response_wrapper(
+ permission.reply,
+ )
+
+ @cached_property
+ def request(self) -> RequestResourceWithRawResponse:
+ return RequestResourceWithRawResponse(self._permission.request)
+
+ @cached_property
+ def saved(self) -> SavedResourceWithRawResponse:
+ return SavedResourceWithRawResponse(self._permission.saved)
+
+
+class AsyncPermissionResourceWithRawResponse:
+ def __init__(self, permission: AsyncPermissionResource) -> None:
+ self._permission = permission
+
+ self.create = async_to_raw_response_wrapper(
+ permission.create,
+ )
+ self.list = async_to_raw_response_wrapper(
+ permission.list,
+ )
+ self.get = async_to_raw_response_wrapper(
+ permission.get,
+ )
+ self.reply = async_to_raw_response_wrapper(
+ permission.reply,
+ )
+
+ @cached_property
+ def request(self) -> AsyncRequestResourceWithRawResponse:
+ return AsyncRequestResourceWithRawResponse(self._permission.request)
+
+ @cached_property
+ def saved(self) -> AsyncSavedResourceWithRawResponse:
+ return AsyncSavedResourceWithRawResponse(self._permission.saved)
+
+
+class PermissionResourceWithStreamingResponse:
+ def __init__(self, permission: PermissionResource) -> None:
+ self._permission = permission
+
+ self.create = to_streamed_response_wrapper(
+ permission.create,
+ )
+ self.list = to_streamed_response_wrapper(
+ permission.list,
+ )
+ self.get = to_streamed_response_wrapper(
+ permission.get,
+ )
+ self.reply = to_streamed_response_wrapper(
+ permission.reply,
+ )
+
+ @cached_property
+ def request(self) -> RequestResourceWithStreamingResponse:
+ return RequestResourceWithStreamingResponse(self._permission.request)
+
+ @cached_property
+ def saved(self) -> SavedResourceWithStreamingResponse:
+ return SavedResourceWithStreamingResponse(self._permission.saved)
+
+
+class AsyncPermissionResourceWithStreamingResponse:
+ def __init__(self, permission: AsyncPermissionResource) -> None:
+ self._permission = permission
+
+ self.create = async_to_streamed_response_wrapper(
+ permission.create,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ permission.list,
+ )
+ self.get = async_to_streamed_response_wrapper(
+ permission.get,
+ )
+ self.reply = async_to_streamed_response_wrapper(
+ permission.reply,
+ )
+
+ @cached_property
+ def request(self) -> AsyncRequestResourceWithStreamingResponse:
+ return AsyncRequestResourceWithStreamingResponse(self._permission.request)
+
+ @cached_property
+ def saved(self) -> AsyncSavedResourceWithStreamingResponse:
+ return AsyncSavedResourceWithStreamingResponse(self._permission.saved)
diff --git a/src/opencode/resources/permission/request.py b/src/opencode/resources/permission/request.py
new file mode 100644
index 0000000..b371eb5
--- /dev/null
+++ b/src/opencode/resources/permission/request.py
@@ -0,0 +1,171 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.permission import request_list_params
+from ...types.permission.request_list_response import RequestListResponse
+
+__all__ = ["RequestResource", "AsyncRequestResource"]
+
+
+class RequestResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> RequestResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return RequestResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> RequestResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return RequestResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ *,
+ location: Optional[request_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> RequestListResponse:
+ """
+ Retrieve pending permission requests for a location.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/permission/request",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, request_list_params.RequestListParams),
+ ),
+ cast_to=RequestListResponse,
+ )
+
+
+class AsyncRequestResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncRequestResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncRequestResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncRequestResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncRequestResourceWithStreamingResponse(self)
+
+ async def list(
+ self,
+ *,
+ location: Optional[request_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> RequestListResponse:
+ """
+ Retrieve pending permission requests for a location.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/permission/request",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, request_list_params.RequestListParams),
+ ),
+ cast_to=RequestListResponse,
+ )
+
+
+class RequestResourceWithRawResponse:
+ def __init__(self, request: RequestResource) -> None:
+ self._request = request
+
+ self.list = to_raw_response_wrapper(
+ request.list,
+ )
+
+
+class AsyncRequestResourceWithRawResponse:
+ def __init__(self, request: AsyncRequestResource) -> None:
+ self._request = request
+
+ self.list = async_to_raw_response_wrapper(
+ request.list,
+ )
+
+
+class RequestResourceWithStreamingResponse:
+ def __init__(self, request: RequestResource) -> None:
+ self._request = request
+
+ self.list = to_streamed_response_wrapper(
+ request.list,
+ )
+
+
+class AsyncRequestResourceWithStreamingResponse:
+ def __init__(self, request: AsyncRequestResource) -> None:
+ self._request = request
+
+ self.list = async_to_streamed_response_wrapper(
+ request.list,
+ )
diff --git a/src/opencode/resources/permission/saved.py b/src/opencode/resources/permission/saved.py
new file mode 100644
index 0000000..2dd95b7
--- /dev/null
+++ b/src/opencode/resources/permission/saved.py
@@ -0,0 +1,352 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.permission import saved_list_params, saved_update_params
+from ...types.shared.project_id import ProjectID
+from ...types.shared.permission_saved_id import PermissionSavedID
+from ...types.permission.saved_list_response import SavedListResponse
+from ...types.permission.saved_update_response import SavedUpdateResponse
+
+__all__ = ["SavedResource", "AsyncSavedResource"]
+
+
+class SavedResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> SavedResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return SavedResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> SavedResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return SavedResourceWithStreamingResponse(self)
+
+ def update(
+ self,
+ id: PermissionSavedID,
+ *,
+ action: Optional[str] | Omit = omit,
+ resource: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SavedUpdateResponse:
+ """
+ Change the action or resource of a saved permission. Fails with a conflict when
+ the project already has a saved permission with the same action and resource.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._patch(
+ path_template("/api/permission/saved/{id}", id=id),
+ body=maybe_transform(
+ {
+ "action": action,
+ "resource": resource,
+ },
+ saved_update_params.SavedUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SavedUpdateResponse,
+ )
+
+ def list(
+ self,
+ *,
+ project_id: Optional[ProjectID] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SavedListResponse:
+ """
+ Retrieve saved permissions, optionally filtered by project.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/permission/saved",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"project_id": project_id}, saved_list_params.SavedListParams),
+ ),
+ cast_to=SavedListResponse,
+ )
+
+ def remove(
+ self,
+ id: PermissionSavedID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Remove a saved permission by ID.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template("/api/permission/saved/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncSavedResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncSavedResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncSavedResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncSavedResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncSavedResourceWithStreamingResponse(self)
+
+ async def update(
+ self,
+ id: PermissionSavedID,
+ *,
+ action: Optional[str] | Omit = omit,
+ resource: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SavedUpdateResponse:
+ """
+ Change the action or resource of a saved permission. Fails with a conflict when
+ the project already has a saved permission with the same action and resource.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._patch(
+ path_template("/api/permission/saved/{id}", id=id),
+ body=await async_maybe_transform(
+ {
+ "action": action,
+ "resource": resource,
+ },
+ saved_update_params.SavedUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SavedUpdateResponse,
+ )
+
+ async def list(
+ self,
+ *,
+ project_id: Optional[ProjectID] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SavedListResponse:
+ """
+ Retrieve saved permissions, optionally filtered by project.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/permission/saved",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"project_id": project_id}, saved_list_params.SavedListParams),
+ ),
+ cast_to=SavedListResponse,
+ )
+
+ async def remove(
+ self,
+ id: PermissionSavedID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Remove a saved permission by ID.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template("/api/permission/saved/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class SavedResourceWithRawResponse:
+ def __init__(self, saved: SavedResource) -> None:
+ self._saved = saved
+
+ self.update = to_raw_response_wrapper(
+ saved.update,
+ )
+ self.list = to_raw_response_wrapper(
+ saved.list,
+ )
+ self.remove = to_raw_response_wrapper(
+ saved.remove,
+ )
+
+
+class AsyncSavedResourceWithRawResponse:
+ def __init__(self, saved: AsyncSavedResource) -> None:
+ self._saved = saved
+
+ self.update = async_to_raw_response_wrapper(
+ saved.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ saved.list,
+ )
+ self.remove = async_to_raw_response_wrapper(
+ saved.remove,
+ )
+
+
+class SavedResourceWithStreamingResponse:
+ def __init__(self, saved: SavedResource) -> None:
+ self._saved = saved
+
+ self.update = to_streamed_response_wrapper(
+ saved.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ saved.list,
+ )
+ self.remove = to_streamed_response_wrapper(
+ saved.remove,
+ )
+
+
+class AsyncSavedResourceWithStreamingResponse:
+ def __init__(self, saved: AsyncSavedResource) -> None:
+ self._saved = saved
+
+ self.update = async_to_streamed_response_wrapper(
+ saved.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ saved.list,
+ )
+ self.remove = async_to_streamed_response_wrapper(
+ saved.remove,
+ )
diff --git a/src/opencode/resources/plugin.py b/src/opencode/resources/plugin.py
index f944c70..7e0adcd 100644
--- a/src/opencode/resources/plugin.py
+++ b/src/opencode/resources/plugin.py
@@ -1,323 +1,348 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``plugin`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from collections.abc import Sequence
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
+from typing import Optional
+
+import httpx
-from .._base_client import RequestOptions
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import plugin_list_params, plugin_check_params, plugin_update_params
+from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- Location,
- PluginCheckResponse,
- PluginListResponse,
- )
+from .._base_client import make_request_options
+from ..types.plugin_list_response import PluginListResponse
+from ..types.plugin_check_response import PluginCheckResponse
__all__ = ["PluginResource", "AsyncPluginResource"]
class PluginResource(SyncAPIResource):
- """Operations under ``client.plugin`` (OpenCode ``plugin``)."""
-
@cached_property
def with_raw_response(self) -> PluginResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return PluginResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> PluginResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return PluginResourceWithStreamingResponse(self)
- def list(
+ def update(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ targets: SequenceNotStr[str],
+ location: Optional[plugin_update_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PluginListResponse:
- """List plugins.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Update package plugins concurrently and notify active locations to reload them.
+ Responds once every update has finished; fails when any update fails.
- Retrieve enabled server plugins and their current status.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ extra_body: Add additional JSON properties to the request
- ``GET /api/plugin``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "PluginListResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/plugin",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PluginListResponse",
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ "/api/plugin/update",
+ body=maybe_transform({"targets": targets}, plugin_update_params.PluginUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, plugin_update_params.PluginUpdateParams),
),
+ cast_to=NoneType,
)
- def check(
+ def list(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
- target: str | NotGiven | None = NOT_GIVEN,
+ location: Optional[plugin_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PluginCheckResponse:
- """Check plugin updates.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PluginListResponse:
+ """
+ Retrieve enabled server plugins and their current status.
- Check one or all package plugins for available updates.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ extra_body: Add additional JSON properties to the request
- ``POST /api/plugin/check``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "PluginCheckResponse",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/plugin/check",
- query={"location": self._location(location)},
- body={"target": target},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PluginCheckResponse",
+ return self._get(
+ "/api/plugin",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, plugin_list_params.PluginListParams),
),
+ cast_to=PluginListResponse,
)
- def update(
+ def check(
self,
*,
- targets: Sequence[str],
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[plugin_check_params.Location] | Omit = omit,
+ target: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Update plugins.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PluginCheckResponse:
+ """
+ Check one or all package plugins for available updates.
+
+ Args:
+ extra_headers: Send extra headers
- Update package plugins concurrently and notify active locations to reload them. Responds once every update has finished; fails when any update fails.
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
+ extra_body: Add additional JSON properties to the request
- ``POST /api/plugin/update``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/plugin/update",
- query={"location": self._location(location)},
- body={"targets": targets},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
+ return self._post(
+ "/api/plugin/check",
+ body=maybe_transform({"target": target}, plugin_check_params.PluginCheckParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, plugin_check_params.PluginCheckParams),
),
+ cast_to=PluginCheckResponse,
)
-class PluginResourceWithRawResponse:
- """``client.plugin.with_raw_response``."""
-
- def __init__(self, resource: PluginResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
- self.check = to_raw_response_wrapper(resource.check)
- self.update = to_raw_response_wrapper(resource.update)
-
-
-class PluginResourceWithStreamingResponse:
- """``client.plugin.with_streaming_response``."""
-
- def __init__(self, resource: PluginResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
- self.check = to_streamed_response_wrapper(resource.check)
- self.update = to_streamed_response_wrapper(resource.update)
-
-
class AsyncPluginResource(AsyncAPIResource):
- """Operations under ``client.plugin`` (OpenCode ``plugin``)."""
-
@cached_property
def with_raw_response(self) -> AsyncPluginResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncPluginResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncPluginResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncPluginResourceWithStreamingResponse(self)
- async def list(
+ async def update(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ targets: SequenceNotStr[str],
+ location: Optional[plugin_update_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PluginListResponse:
- """List plugins.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Update package plugins concurrently and notify active locations to reload them.
+ Responds once every update has finished; fails when any update fails.
- Retrieve enabled server plugins and their current status.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ extra_body: Add additional JSON properties to the request
- ``GET /api/plugin``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "PluginListResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/plugin",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PluginListResponse",
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ "/api/plugin/update",
+ body=await async_maybe_transform({"targets": targets}, plugin_update_params.PluginUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, plugin_update_params.PluginUpdateParams),
),
+ cast_to=NoneType,
)
- async def check(
+ async def list(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
- target: str | NotGiven | None = NOT_GIVEN,
+ location: Optional[plugin_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PluginCheckResponse:
- """Check plugin updates.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PluginListResponse:
+ """
+ Retrieve enabled server plugins and their current status.
- Check one or all package plugins for available updates.
+ Args:
+ extra_headers: Send extra headers
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ extra_query: Add additional query parameters to the request
- ``POST /api/plugin/check``
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "PluginCheckResponse",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/plugin/check",
- query={"location": self._location(location)},
- body={"target": target},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PluginCheckResponse",
+ return await self._get(
+ "/api/plugin",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, plugin_list_params.PluginListParams),
),
+ cast_to=PluginListResponse,
)
- async def update(
+ async def check(
self,
*,
- targets: Sequence[str],
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[plugin_check_params.Location] | Omit = omit,
+ target: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Update plugins.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PluginCheckResponse:
+ """
+ Check one or all package plugins for available updates.
+
+ Args:
+ extra_headers: Send extra headers
- Update package plugins concurrently and notify active locations to reload them. Responds once every update has finished; fails when any update fails.
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
+ extra_body: Add additional JSON properties to the request
- ``POST /api/plugin/update``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/plugin/update",
- query={"location": self._location(location)},
- body={"targets": targets},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
+ return await self._post(
+ "/api/plugin/check",
+ body=await async_maybe_transform({"target": target}, plugin_check_params.PluginCheckParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, plugin_check_params.PluginCheckParams),
),
+ cast_to=PluginCheckResponse,
+ )
+
+
+class PluginResourceWithRawResponse:
+ def __init__(self, plugin: PluginResource) -> None:
+ self._plugin = plugin
+
+ self.update = to_raw_response_wrapper(
+ plugin.update,
+ )
+ self.list = to_raw_response_wrapper(
+ plugin.list,
+ )
+ self.check = to_raw_response_wrapper(
+ plugin.check,
)
class AsyncPluginResourceWithRawResponse:
- """``client.plugin.with_raw_response``."""
+ def __init__(self, plugin: AsyncPluginResource) -> None:
+ self._plugin = plugin
+
+ self.update = async_to_raw_response_wrapper(
+ plugin.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ plugin.list,
+ )
+ self.check = async_to_raw_response_wrapper(
+ plugin.check,
+ )
- def __init__(self, resource: AsyncPluginResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
- self.check = async_to_raw_response_wrapper(resource.check)
- self.update = async_to_raw_response_wrapper(resource.update)
+
+class PluginResourceWithStreamingResponse:
+ def __init__(self, plugin: PluginResource) -> None:
+ self._plugin = plugin
+
+ self.update = to_streamed_response_wrapper(
+ plugin.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ plugin.list,
+ )
+ self.check = to_streamed_response_wrapper(
+ plugin.check,
+ )
class AsyncPluginResourceWithStreamingResponse:
- """``client.plugin.with_streaming_response``."""
+ def __init__(self, plugin: AsyncPluginResource) -> None:
+ self._plugin = plugin
- def __init__(self, resource: AsyncPluginResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.check = async_to_streamed_response_wrapper(resource.check)
- self.update = async_to_streamed_response_wrapper(resource.update)
+ self.update = async_to_streamed_response_wrapper(
+ plugin.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ plugin.list,
+ )
+ self.check = async_to_streamed_response_wrapper(
+ plugin.check,
+ )
diff --git a/src/opencode/resources/project.py b/src/opencode/resources/project.py
index 9d34230..60cc06e 100644
--- a/src/opencode/resources/project.py
+++ b/src/opencode/resources/project.py
@@ -1,250 +1,282 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``project`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-import builtins
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
+from typing import Optional
+
+import httpx
-from .._base_client import RequestOptions
-from .._qs import encode_path_segment
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import project_list_params, project_update_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import path_template, maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- AbsolutePath,
- Project,
- ProjectCommands,
- ProjectIcon,
- ProjectID,
- )
+from .._base_client import make_request_options
+from ..types.shared.project import Project
+from ..types.shared.project_id import ProjectID
+from ..types.shared.project_icon import ProjectIcon
+from ..types.shared.absolute_path import AbsolutePath
+from ..types.project_list_response import ProjectListResponse
+from ..types.shared.project_commands import ProjectCommands
__all__ = ["ProjectResource", "AsyncProjectResource"]
class ProjectResource(SyncAPIResource):
- """Operations under ``client.project`` (OpenCode ``project``)."""
-
@cached_property
def with_raw_response(self) -> ProjectResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return ProjectResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> ProjectResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return ProjectResourceWithStreamingResponse(self)
- def list(
+ def update(
self,
+ project_id: ProjectID,
*,
- archived: bool | NotGiven | None = NOT_GIVEN,
+ canonical: AbsolutePath | Omit = omit,
+ commands: ProjectCommands | Omit = omit,
+ icon: ProjectIcon | Omit = omit,
+ name: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[Project]:
- """List projects.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Project:
+ """
+ Update the project canonical directory, display metadata, and workspace
+ commands.
- List known projects. Archived projects are excluded unless archived=true.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/project``
- """
- return cast(
- "builtins.list[Project]",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/project",
- query={"archived": archived},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[Project]",
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not project_id:
+ raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+ return self._patch(
+ path_template("/api/project/{project_id}", project_id=project_id),
+ body=maybe_transform(
+ {
+ "canonical": canonical,
+ "commands": commands,
+ "icon": icon,
+ "name": name,
+ },
+ project_update_params.ProjectUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=Project,
)
- def update(
+ def list(
self,
- project_id: ProjectID,
*,
- canonical: AbsolutePath | NotGiven = NOT_GIVEN,
- name: str | NotGiven = NOT_GIVEN,
- icon: ProjectIcon | NotGiven = NOT_GIVEN,
- commands: ProjectCommands | NotGiven = NOT_GIVEN,
+ archived: Optional[bool] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> Project:
- """Update project.
-
- Update the project canonical directory, display metadata, and workspace commands.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ProjectNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``PATCH /api/project/:projectID``
- """
- return cast(
- "Project",
- self._client.request(
- RequestOptions(
- method="PATCH",
- path=f"/api/project/{encode_path_segment(project_id)}",
- body={"canonical": canonical, "name": name, "icon": icon, "commands": commands},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="Project",
- ),
- )
-
-
-class ProjectResourceWithRawResponse:
- """``client.project.with_raw_response``."""
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ProjectListResponse:
+ """
+ List known projects. Archived projects are excluded unless archived=true.
- def __init__(self, resource: ProjectResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
- self.update = to_raw_response_wrapper(resource.update)
+ Args:
+ extra_headers: Send extra headers
+ extra_query: Add additional query parameters to the request
-class ProjectResourceWithStreamingResponse:
- """``client.project.with_streaming_response``."""
+ extra_body: Add additional JSON properties to the request
- def __init__(self, resource: ProjectResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
- self.update = to_streamed_response_wrapper(resource.update)
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/project",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"archived": archived}, project_list_params.ProjectListParams),
+ ),
+ cast_to=ProjectListResponse,
+ )
class AsyncProjectResource(AsyncAPIResource):
- """Operations under ``client.project`` (OpenCode ``project``)."""
-
@cached_property
def with_raw_response(self) -> AsyncProjectResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncProjectResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncProjectResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncProjectResourceWithStreamingResponse(self)
- async def list(
+ async def update(
self,
+ project_id: ProjectID,
*,
- archived: bool | NotGiven | None = NOT_GIVEN,
+ canonical: AbsolutePath | Omit = omit,
+ commands: ProjectCommands | Omit = omit,
+ icon: ProjectIcon | Omit = omit,
+ name: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[Project]:
- """List projects.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Project:
+ """
+ Update the project canonical directory, display metadata, and workspace
+ commands.
- List known projects. Archived projects are excluded unless archived=true.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/project``
- """
- return cast(
- "builtins.list[Project]",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/project",
- query={"archived": archived},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[Project]",
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not project_id:
+ raise ValueError(f"Expected a non-empty value for `project_id` but received {project_id!r}")
+ return await self._patch(
+ path_template("/api/project/{project_id}", project_id=project_id),
+ body=await async_maybe_transform(
+ {
+ "canonical": canonical,
+ "commands": commands,
+ "icon": icon,
+ "name": name,
+ },
+ project_update_params.ProjectUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=Project,
)
- async def update(
+ async def list(
self,
- project_id: ProjectID,
*,
- canonical: AbsolutePath | NotGiven = NOT_GIVEN,
- name: str | NotGiven = NOT_GIVEN,
- icon: ProjectIcon | NotGiven = NOT_GIVEN,
- commands: ProjectCommands | NotGiven = NOT_GIVEN,
+ archived: Optional[bool] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> Project:
- """Update project.
-
- Update the project canonical directory, display metadata, and workspace commands.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ProjectNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``PATCH /api/project/:projectID``
- """
- return cast(
- "Project",
- await self._client.request(
- RequestOptions(
- method="PATCH",
- path=f"/api/project/{encode_path_segment(project_id)}",
- body={"canonical": canonical, "name": name, "icon": icon, "commands": commands},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="Project",
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ProjectListResponse:
+ """
+ List known projects. Archived projects are excluded unless archived=true.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/project",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"archived": archived}, project_list_params.ProjectListParams),
),
+ cast_to=ProjectListResponse,
+ )
+
+
+class ProjectResourceWithRawResponse:
+ def __init__(self, project: ProjectResource) -> None:
+ self._project = project
+
+ self.update = to_raw_response_wrapper(
+ project.update,
+ )
+ self.list = to_raw_response_wrapper(
+ project.list,
)
class AsyncProjectResourceWithRawResponse:
- """``client.project.with_raw_response``."""
+ def __init__(self, project: AsyncProjectResource) -> None:
+ self._project = project
- def __init__(self, resource: AsyncProjectResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
- self.update = async_to_raw_response_wrapper(resource.update)
+ self.update = async_to_raw_response_wrapper(
+ project.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ project.list,
+ )
+
+
+class ProjectResourceWithStreamingResponse:
+ def __init__(self, project: ProjectResource) -> None:
+ self._project = project
+
+ self.update = to_streamed_response_wrapper(
+ project.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ project.list,
+ )
class AsyncProjectResourceWithStreamingResponse:
- """``client.project.with_streaming_response``."""
+ def __init__(self, project: AsyncProjectResource) -> None:
+ self._project = project
- def __init__(self, resource: AsyncProjectResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.update = async_to_streamed_response_wrapper(resource.update)
+ self.update = async_to_streamed_response_wrapper(
+ project.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ project.list,
+ )
diff --git a/src/opencode/resources/provider.py b/src/opencode/resources/provider.py
index 0f7f480..b910b34 100644
--- a/src/opencode/resources/provider.py
+++ b/src/opencode/resources/provider.py
@@ -1,246 +1,265 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``provider`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
+from typing import Optional
+
+import httpx
-from .._base_client import RequestOptions
-from .._qs import encode_path_segment
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import provider_get_params, provider_list_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import path_template, maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- Location,
- ProviderGetResponse,
- ProviderID,
- ProviderListResponse,
- )
+from .._base_client import make_request_options
+from ..types.shared.provider_id import ProviderID
+from ..types.provider_get_response import ProviderGetResponse
+from ..types.provider_list_response import ProviderListResponse
__all__ = ["ProviderResource", "AsyncProviderResource"]
class ProviderResource(SyncAPIResource):
- """Operations under ``client.provider`` (OpenCode ``provider``)."""
-
@cached_property
def with_raw_response(self) -> ProviderResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return ProviderResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> ProviderResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return ProviderResourceWithStreamingResponse(self)
def list(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[provider_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ProviderListResponse:
- """List providers.
-
- Retrieve active AI providers so clients can show provider availability and configuration.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/provider``
- """
- return cast(
- "ProviderListResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/provider",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ProviderListResponse",
+ """
+ Retrieve active AI providers so clients can show provider availability and
+ configuration.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/provider",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, provider_list_params.ProviderListParams),
),
+ cast_to=ProviderListResponse,
)
def get(
self,
provider_id: ProviderID,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[provider_get_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ProviderGetResponse:
- """Get provider.
-
- Retrieve a single AI provider so clients can inspect its availability and endpoint settings.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ProviderNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/provider/:providerID``
- """
- return cast(
- "ProviderGetResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/provider/{encode_path_segment(provider_id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ProviderGetResponse",
- ),
- )
-
-
-class ProviderResourceWithRawResponse:
- """``client.provider.with_raw_response``."""
+ """
+ Retrieve a single AI provider so clients can inspect its availability and
+ endpoint settings.
- def __init__(self, resource: ProviderResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
- self.get = to_raw_response_wrapper(resource.get)
+ Args:
+ extra_headers: Send extra headers
+ extra_query: Add additional query parameters to the request
-class ProviderResourceWithStreamingResponse:
- """``client.provider.with_streaming_response``."""
+ extra_body: Add additional JSON properties to the request
- def __init__(self, resource: ProviderResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
- self.get = to_streamed_response_wrapper(resource.get)
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not provider_id:
+ raise ValueError(f"Expected a non-empty value for `provider_id` but received {provider_id!r}")
+ return self._get(
+ path_template("/api/provider/{provider_id}", provider_id=provider_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, provider_get_params.ProviderGetParams),
+ ),
+ cast_to=ProviderGetResponse,
+ )
class AsyncProviderResource(AsyncAPIResource):
- """Operations under ``client.provider`` (OpenCode ``provider``)."""
-
@cached_property
def with_raw_response(self) -> AsyncProviderResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncProviderResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncProviderResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncProviderResourceWithStreamingResponse(self)
async def list(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[provider_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ProviderListResponse:
- """List providers.
-
- Retrieve active AI providers so clients can show provider availability and configuration.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/provider``
- """
- return cast(
- "ProviderListResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/provider",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ProviderListResponse",
+ """
+ Retrieve active AI providers so clients can show provider availability and
+ configuration.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/provider",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, provider_list_params.ProviderListParams),
),
+ cast_to=ProviderListResponse,
)
async def get(
self,
provider_id: ProviderID,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[provider_get_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ProviderGetResponse:
- """Get provider.
-
- Retrieve a single AI provider so clients can inspect its availability and endpoint settings.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ProviderNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/provider/:providerID``
- """
- return cast(
- "ProviderGetResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/provider/{encode_path_segment(provider_id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ProviderGetResponse",
+ """
+ Retrieve a single AI provider so clients can inspect its availability and
+ endpoint settings.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not provider_id:
+ raise ValueError(f"Expected a non-empty value for `provider_id` but received {provider_id!r}")
+ return await self._get(
+ path_template("/api/provider/{provider_id}", provider_id=provider_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, provider_get_params.ProviderGetParams),
),
+ cast_to=ProviderGetResponse,
+ )
+
+
+class ProviderResourceWithRawResponse:
+ def __init__(self, provider: ProviderResource) -> None:
+ self._provider = provider
+
+ self.list = to_raw_response_wrapper(
+ provider.list,
+ )
+ self.get = to_raw_response_wrapper(
+ provider.get,
)
class AsyncProviderResourceWithRawResponse:
- """``client.provider.with_raw_response``."""
+ def __init__(self, provider: AsyncProviderResource) -> None:
+ self._provider = provider
+
+ self.list = async_to_raw_response_wrapper(
+ provider.list,
+ )
+ self.get = async_to_raw_response_wrapper(
+ provider.get,
+ )
+
+
+class ProviderResourceWithStreamingResponse:
+ def __init__(self, provider: ProviderResource) -> None:
+ self._provider = provider
- def __init__(self, resource: AsyncProviderResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
- self.get = async_to_raw_response_wrapper(resource.get)
+ self.list = to_streamed_response_wrapper(
+ provider.list,
+ )
+ self.get = to_streamed_response_wrapper(
+ provider.get,
+ )
class AsyncProviderResourceWithStreamingResponse:
- """``client.provider.with_streaming_response``."""
+ def __init__(self, provider: AsyncProviderResource) -> None:
+ self._provider = provider
- def __init__(self, resource: AsyncProviderResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.get = async_to_streamed_response_wrapper(resource.get)
+ self.list = async_to_streamed_response_wrapper(
+ provider.list,
+ )
+ self.get = async_to_streamed_response_wrapper(
+ provider.get,
+ )
diff --git a/src/opencode/resources/pty/__init__.py b/src/opencode/resources/pty/__init__.py
new file mode 100644
index 0000000..55fb504
--- /dev/null
+++ b/src/opencode/resources/pty/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from .pty import (
+ PtyResource,
+ AsyncPtyResource,
+ PtyResourceWithRawResponse,
+ AsyncPtyResourceWithRawResponse,
+ PtyResourceWithStreamingResponse,
+ AsyncPtyResourceWithStreamingResponse,
+)
+from .connect import (
+ ConnectResource,
+ AsyncConnectResource,
+ ConnectResourceWithRawResponse,
+ AsyncConnectResourceWithRawResponse,
+ ConnectResourceWithStreamingResponse,
+ AsyncConnectResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "ConnectResource",
+ "AsyncConnectResource",
+ "ConnectResourceWithRawResponse",
+ "AsyncConnectResourceWithRawResponse",
+ "ConnectResourceWithStreamingResponse",
+ "AsyncConnectResourceWithStreamingResponse",
+ "PtyResource",
+ "AsyncPtyResource",
+ "PtyResourceWithRawResponse",
+ "AsyncPtyResourceWithRawResponse",
+ "PtyResourceWithStreamingResponse",
+ "AsyncPtyResourceWithStreamingResponse",
+]
diff --git a/src/opencode/resources/pty/connect.py b/src/opencode/resources/pty/connect.py
new file mode 100644
index 0000000..da06adc
--- /dev/null
+++ b/src/opencode/resources/pty/connect.py
@@ -0,0 +1,192 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ..._utils import is_given, path_template, maybe_transform, strip_not_given, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...types.pty import connect_token_params
+from ..._base_client import make_request_options
+from ...types.shared.pty_id import PtyID
+from ...types.pty.connect_token_response import ConnectTokenResponse
+
+__all__ = ["ConnectResource", "AsyncConnectResource"]
+
+
+class ConnectResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> ConnectResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return ConnectResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ConnectResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return ConnectResourceWithStreamingResponse(self)
+
+ def token(
+ self,
+ pty_id: PtyID,
+ *,
+ location: Optional[connect_token_params.Location] | Omit = omit,
+ x_opencode_ticket: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ConnectTokenResponse:
+ """
+ Create a short-lived single-use ticket for opening a PTY WebSocket connection.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not pty_id:
+ raise ValueError(f"Expected a non-empty value for `pty_id` but received {pty_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {"x-opencode-ticket": str(x_opencode_ticket) if is_given(x_opencode_ticket) else not_given}
+ ),
+ **(extra_headers or {}),
+ }
+ return self._post(
+ path_template("/api/pty/{pty_id}/connect-token", pty_id=pty_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, connect_token_params.ConnectTokenParams),
+ ),
+ cast_to=ConnectTokenResponse,
+ )
+
+
+class AsyncConnectResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncConnectResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncConnectResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncConnectResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncConnectResourceWithStreamingResponse(self)
+
+ async def token(
+ self,
+ pty_id: PtyID,
+ *,
+ location: Optional[connect_token_params.Location] | Omit = omit,
+ x_opencode_ticket: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ConnectTokenResponse:
+ """
+ Create a short-lived single-use ticket for opening a PTY WebSocket connection.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not pty_id:
+ raise ValueError(f"Expected a non-empty value for `pty_id` but received {pty_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {"x-opencode-ticket": str(x_opencode_ticket) if is_given(x_opencode_ticket) else not_given}
+ ),
+ **(extra_headers or {}),
+ }
+ return await self._post(
+ path_template("/api/pty/{pty_id}/connect-token", pty_id=pty_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, connect_token_params.ConnectTokenParams),
+ ),
+ cast_to=ConnectTokenResponse,
+ )
+
+
+class ConnectResourceWithRawResponse:
+ def __init__(self, connect: ConnectResource) -> None:
+ self._connect = connect
+
+ self.token = to_raw_response_wrapper(
+ connect.token,
+ )
+
+
+class AsyncConnectResourceWithRawResponse:
+ def __init__(self, connect: AsyncConnectResource) -> None:
+ self._connect = connect
+
+ self.token = async_to_raw_response_wrapper(
+ connect.token,
+ )
+
+
+class ConnectResourceWithStreamingResponse:
+ def __init__(self, connect: ConnectResource) -> None:
+ self._connect = connect
+
+ self.token = to_streamed_response_wrapper(
+ connect.token,
+ )
+
+
+class AsyncConnectResourceWithStreamingResponse:
+ def __init__(self, connect: AsyncConnectResource) -> None:
+ self._connect = connect
+
+ self.token = async_to_streamed_response_wrapper(
+ connect.token,
+ )
diff --git a/src/opencode/resources/pty/pty.py b/src/opencode/resources/pty/pty.py
new file mode 100644
index 0000000..992d40a
--- /dev/null
+++ b/src/opencode/resources/pty/pty.py
@@ -0,0 +1,605 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Optional
+
+import httpx
+
+from ...types import pty_get_params, pty_list_params, pty_create_params, pty_remove_params, pty_update_params
+from .connect import (
+ ConnectResource,
+ AsyncConnectResource,
+ ConnectResourceWithRawResponse,
+ AsyncConnectResourceWithRawResponse,
+ ConnectResourceWithStreamingResponse,
+ AsyncConnectResourceWithStreamingResponse,
+)
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.shared.pty_id import PtyID
+from ...types.pty_get_response import PtyGetResponse
+from ...types.pty_list_response import PtyListResponse
+from ...types.pty_create_response import PtyCreateResponse
+from ...types.pty_update_response import PtyUpdateResponse
+
+__all__ = ["PtyResource", "AsyncPtyResource"]
+
+
+class PtyResource(SyncAPIResource):
+ @cached_property
+ def connect(self) -> ConnectResource:
+ return ConnectResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> PtyResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return PtyResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> PtyResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return PtyResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ args: SequenceNotStr[str] | Omit = omit,
+ command: str | Omit = omit,
+ cwd: str | Omit = omit,
+ env: Dict[str, str] | Omit = omit,
+ location: Optional[pty_create_params.Location] | Omit = omit,
+ title: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PtyCreateResponse:
+ """
+ Create a pseudo-terminal session for a location.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/api/pty",
+ body=maybe_transform(
+ {
+ "args": args,
+ "command": command,
+ "cwd": cwd,
+ "env": env,
+ "title": title,
+ },
+ pty_create_params.PtyCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, pty_create_params.PtyCreateParams),
+ ),
+ cast_to=PtyCreateResponse,
+ )
+
+ def update(
+ self,
+ pty_id: PtyID,
+ *,
+ location: Optional[pty_update_params.Location] | Omit = omit,
+ size: pty_update_params.Size | Omit = omit,
+ title: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PtyUpdateResponse:
+ """
+ Update the title or viewport size of one PTY session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not pty_id:
+ raise ValueError(f"Expected a non-empty value for `pty_id` but received {pty_id!r}")
+ return self._put(
+ path_template("/api/pty/{pty_id}", pty_id=pty_id),
+ body=maybe_transform(
+ {
+ "size": size,
+ "title": title,
+ },
+ pty_update_params.PtyUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, pty_update_params.PtyUpdateParams),
+ ),
+ cast_to=PtyUpdateResponse,
+ )
+
+ def list(
+ self,
+ *,
+ location: Optional[pty_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PtyListResponse:
+ """
+ List PTY sessions for a location, including exited sessions retained until
+ removal.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/pty",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, pty_list_params.PtyListParams),
+ ),
+ cast_to=PtyListResponse,
+ )
+
+ def get(
+ self,
+ pty_id: PtyID,
+ *,
+ location: Optional[pty_get_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PtyGetResponse:
+ """
+ Get one PTY session, including its exit code once exited.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not pty_id:
+ raise ValueError(f"Expected a non-empty value for `pty_id` but received {pty_id!r}")
+ return self._get(
+ path_template("/api/pty/{pty_id}", pty_id=pty_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, pty_get_params.PtyGetParams),
+ ),
+ cast_to=PtyGetResponse,
+ )
+
+ def remove(
+ self,
+ pty_id: PtyID,
+ *,
+ location: Optional[pty_remove_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Terminate and remove one PTY session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not pty_id:
+ raise ValueError(f"Expected a non-empty value for `pty_id` but received {pty_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template("/api/pty/{pty_id}", pty_id=pty_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, pty_remove_params.PtyRemoveParams),
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncPtyResource(AsyncAPIResource):
+ @cached_property
+ def connect(self) -> AsyncConnectResource:
+ return AsyncConnectResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncPtyResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncPtyResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncPtyResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncPtyResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ args: SequenceNotStr[str] | Omit = omit,
+ command: str | Omit = omit,
+ cwd: str | Omit = omit,
+ env: Dict[str, str] | Omit = omit,
+ location: Optional[pty_create_params.Location] | Omit = omit,
+ title: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PtyCreateResponse:
+ """
+ Create a pseudo-terminal session for a location.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/api/pty",
+ body=await async_maybe_transform(
+ {
+ "args": args,
+ "command": command,
+ "cwd": cwd,
+ "env": env,
+ "title": title,
+ },
+ pty_create_params.PtyCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, pty_create_params.PtyCreateParams),
+ ),
+ cast_to=PtyCreateResponse,
+ )
+
+ async def update(
+ self,
+ pty_id: PtyID,
+ *,
+ location: Optional[pty_update_params.Location] | Omit = omit,
+ size: pty_update_params.Size | Omit = omit,
+ title: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PtyUpdateResponse:
+ """
+ Update the title or viewport size of one PTY session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not pty_id:
+ raise ValueError(f"Expected a non-empty value for `pty_id` but received {pty_id!r}")
+ return await self._put(
+ path_template("/api/pty/{pty_id}", pty_id=pty_id),
+ body=await async_maybe_transform(
+ {
+ "size": size,
+ "title": title,
+ },
+ pty_update_params.PtyUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, pty_update_params.PtyUpdateParams),
+ ),
+ cast_to=PtyUpdateResponse,
+ )
+
+ async def list(
+ self,
+ *,
+ location: Optional[pty_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PtyListResponse:
+ """
+ List PTY sessions for a location, including exited sessions retained until
+ removal.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/pty",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, pty_list_params.PtyListParams),
+ ),
+ cast_to=PtyListResponse,
+ )
+
+ async def get(
+ self,
+ pty_id: PtyID,
+ *,
+ location: Optional[pty_get_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PtyGetResponse:
+ """
+ Get one PTY session, including its exit code once exited.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not pty_id:
+ raise ValueError(f"Expected a non-empty value for `pty_id` but received {pty_id!r}")
+ return await self._get(
+ path_template("/api/pty/{pty_id}", pty_id=pty_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, pty_get_params.PtyGetParams),
+ ),
+ cast_to=PtyGetResponse,
+ )
+
+ async def remove(
+ self,
+ pty_id: PtyID,
+ *,
+ location: Optional[pty_remove_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Terminate and remove one PTY session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not pty_id:
+ raise ValueError(f"Expected a non-empty value for `pty_id` but received {pty_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template("/api/pty/{pty_id}", pty_id=pty_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, pty_remove_params.PtyRemoveParams),
+ ),
+ cast_to=NoneType,
+ )
+
+
+class PtyResourceWithRawResponse:
+ def __init__(self, pty: PtyResource) -> None:
+ self._pty = pty
+
+ self.create = to_raw_response_wrapper(
+ pty.create,
+ )
+ self.update = to_raw_response_wrapper(
+ pty.update,
+ )
+ self.list = to_raw_response_wrapper(
+ pty.list,
+ )
+ self.get = to_raw_response_wrapper(
+ pty.get,
+ )
+ self.remove = to_raw_response_wrapper(
+ pty.remove,
+ )
+
+ @cached_property
+ def connect(self) -> ConnectResourceWithRawResponse:
+ return ConnectResourceWithRawResponse(self._pty.connect)
+
+
+class AsyncPtyResourceWithRawResponse:
+ def __init__(self, pty: AsyncPtyResource) -> None:
+ self._pty = pty
+
+ self.create = async_to_raw_response_wrapper(
+ pty.create,
+ )
+ self.update = async_to_raw_response_wrapper(
+ pty.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ pty.list,
+ )
+ self.get = async_to_raw_response_wrapper(
+ pty.get,
+ )
+ self.remove = async_to_raw_response_wrapper(
+ pty.remove,
+ )
+
+ @cached_property
+ def connect(self) -> AsyncConnectResourceWithRawResponse:
+ return AsyncConnectResourceWithRawResponse(self._pty.connect)
+
+
+class PtyResourceWithStreamingResponse:
+ def __init__(self, pty: PtyResource) -> None:
+ self._pty = pty
+
+ self.create = to_streamed_response_wrapper(
+ pty.create,
+ )
+ self.update = to_streamed_response_wrapper(
+ pty.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ pty.list,
+ )
+ self.get = to_streamed_response_wrapper(
+ pty.get,
+ )
+ self.remove = to_streamed_response_wrapper(
+ pty.remove,
+ )
+
+ @cached_property
+ def connect(self) -> ConnectResourceWithStreamingResponse:
+ return ConnectResourceWithStreamingResponse(self._pty.connect)
+
+
+class AsyncPtyResourceWithStreamingResponse:
+ def __init__(self, pty: AsyncPtyResource) -> None:
+ self._pty = pty
+
+ self.create = async_to_streamed_response_wrapper(
+ pty.create,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ pty.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ pty.list,
+ )
+ self.get = async_to_streamed_response_wrapper(
+ pty.get,
+ )
+ self.remove = async_to_streamed_response_wrapper(
+ pty.remove,
+ )
+
+ @cached_property
+ def connect(self) -> AsyncConnectResourceWithStreamingResponse:
+ return AsyncConnectResourceWithStreamingResponse(self._pty.connect)
diff --git a/src/opencode/resources/reference.py b/src/opencode/resources/reference.py
index 679d770..d9f21ef 100644
--- a/src/opencode/resources/reference.py
+++ b/src/opencode/resources/reference.py
@@ -1,159 +1,171 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``reference`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
+from typing import Optional
+
+import httpx
-from .._base_client import RequestOptions
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import reference_list_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- Location,
- ReferenceListResponse,
- )
+from .._base_client import make_request_options
+from ..types.reference_list_response import ReferenceListResponse
__all__ = ["ReferenceResource", "AsyncReferenceResource"]
class ReferenceResource(SyncAPIResource):
- """Operations under ``client.reference`` (OpenCode ``reference``)."""
-
@cached_property
def with_raw_response(self) -> ReferenceResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return ReferenceResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> ReferenceResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return ReferenceResourceWithStreamingResponse(self)
def list(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[reference_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ReferenceListResponse:
- """List references.
-
+ """
List references available in the requested location.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ Args:
+ extra_headers: Send extra headers
- ``GET /api/reference``
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "ReferenceListResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/reference",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ReferenceListResponse",
+ return self._get(
+ "/api/reference",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, reference_list_params.ReferenceListParams),
),
+ cast_to=ReferenceListResponse,
)
-class ReferenceResourceWithRawResponse:
- """``client.reference.with_raw_response``."""
-
- def __init__(self, resource: ReferenceResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
-
-
-class ReferenceResourceWithStreamingResponse:
- """``client.reference.with_streaming_response``."""
-
- def __init__(self, resource: ReferenceResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
-
-
class AsyncReferenceResource(AsyncAPIResource):
- """Operations under ``client.reference`` (OpenCode ``reference``)."""
-
@cached_property
def with_raw_response(self) -> AsyncReferenceResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncReferenceResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncReferenceResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncReferenceResourceWithStreamingResponse(self)
async def list(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[reference_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ReferenceListResponse:
- """List references.
-
+ """
List references available in the requested location.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
- ``GET /api/reference``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "ReferenceListResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/reference",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ReferenceListResponse",
+ return await self._get(
+ "/api/reference",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, reference_list_params.ReferenceListParams),
),
+ cast_to=ReferenceListResponse,
+ )
+
+
+class ReferenceResourceWithRawResponse:
+ def __init__(self, reference: ReferenceResource) -> None:
+ self._reference = reference
+
+ self.list = to_raw_response_wrapper(
+ reference.list,
)
class AsyncReferenceResourceWithRawResponse:
- """``client.reference.with_raw_response``."""
+ def __init__(self, reference: AsyncReferenceResource) -> None:
+ self._reference = reference
+
+ self.list = async_to_raw_response_wrapper(
+ reference.list,
+ )
+
+
+class ReferenceResourceWithStreamingResponse:
+ def __init__(self, reference: ReferenceResource) -> None:
+ self._reference = reference
- def __init__(self, resource: AsyncReferenceResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
+ self.list = to_streamed_response_wrapper(
+ reference.list,
+ )
class AsyncReferenceResourceWithStreamingResponse:
- """``client.reference.with_streaming_response``."""
+ def __init__(self, reference: AsyncReferenceResource) -> None:
+ self._reference = reference
- def __init__(self, resource: AsyncReferenceResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
+ self.list = async_to_streamed_response_wrapper(
+ reference.list,
+ )
diff --git a/src/opencode/resources/rpc.py b/src/opencode/resources/rpc.py
index ce78f07..45965b4 100644
--- a/src/opencode/resources/rpc.py
+++ b/src/opencode/resources/rpc.py
@@ -1,172 +1,187 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``rpc`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from functools import cached_property
-from typing import TYPE_CHECKING, Any, cast
+from typing import Optional
+
+import httpx
-from .._base_client import RequestOptions
-from .._qs import encode_path_segment
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import rpc_call_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import path_template, maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- Location,
- RpcOutput,
- )
+from .._base_client import make_request_options
+from ..types.shared.rpc_output import RpcOutput
__all__ = ["RpcResource", "AsyncRpcResource"]
class RpcResource(SyncAPIResource):
- """Operations under ``client.rpc`` (OpenCode ``rpc``)."""
-
@cached_property
def with_raw_response(self) -> RpcResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return RpcResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> RpcResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return RpcResourceWithStreamingResponse(self)
def call(
self,
- rpc_id: str,
method: str,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
- input: Any | NotGiven = NOT_GIVEN,
+ rpc_id: str,
+ input: object | Omit = omit,
+ location: Optional[rpc_call_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> RpcOutput:
- """Call a plugin RPC.
-
+ """
Dispatch a method to the currently registered RPC at the requested location.
- Raises:
- RpcError: The server answered 400.
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- RpcInternalError: The server answered 500.
+ Args:
+ extra_headers: Send extra headers
- ``POST /api/rpc/:rpcID/:method``
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "RpcOutput",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/rpc/{encode_path_segment(rpc_id)}/{encode_path_segment(method)}",
- query={"location": self._location(location)},
- body={"input": input},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="RpcOutput",
+ if not rpc_id:
+ raise ValueError(f"Expected a non-empty value for `rpc_id` but received {rpc_id!r}")
+ if not method:
+ raise ValueError(f"Expected a non-empty value for `method` but received {method!r}")
+ return self._post(
+ path_template("/api/rpc/{rpc_id}/{method}", rpc_id=rpc_id, method=method),
+ body=maybe_transform({"input": input}, rpc_call_params.RpcCallParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, rpc_call_params.RpcCallParams),
),
+ cast_to=RpcOutput,
)
-class RpcResourceWithRawResponse:
- """``client.rpc.with_raw_response``."""
-
- def __init__(self, resource: RpcResource) -> None:
- self._resource = resource
- self.call = to_raw_response_wrapper(resource.call)
-
-
-class RpcResourceWithStreamingResponse:
- """``client.rpc.with_streaming_response``."""
-
- def __init__(self, resource: RpcResource) -> None:
- self._resource = resource
- self.call = to_streamed_response_wrapper(resource.call)
-
-
class AsyncRpcResource(AsyncAPIResource):
- """Operations under ``client.rpc`` (OpenCode ``rpc``)."""
-
@cached_property
def with_raw_response(self) -> AsyncRpcResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncRpcResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncRpcResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncRpcResourceWithStreamingResponse(self)
async def call(
self,
- rpc_id: str,
method: str,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
- input: Any | NotGiven = NOT_GIVEN,
+ rpc_id: str,
+ input: object | Omit = omit,
+ location: Optional[rpc_call_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> RpcOutput:
- """Call a plugin RPC.
-
+ """
Dispatch a method to the currently registered RPC at the requested location.
- Raises:
- RpcError: The server answered 400.
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- RpcInternalError: The server answered 500.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
- ``POST /api/rpc/:rpcID/:method``
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "RpcOutput",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/rpc/{encode_path_segment(rpc_id)}/{encode_path_segment(method)}",
- query={"location": self._location(location)},
- body={"input": input},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="RpcOutput",
+ if not rpc_id:
+ raise ValueError(f"Expected a non-empty value for `rpc_id` but received {rpc_id!r}")
+ if not method:
+ raise ValueError(f"Expected a non-empty value for `method` but received {method!r}")
+ return await self._post(
+ path_template("/api/rpc/{rpc_id}/{method}", rpc_id=rpc_id, method=method),
+ body=await async_maybe_transform({"input": input}, rpc_call_params.RpcCallParams),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, rpc_call_params.RpcCallParams),
),
+ cast_to=RpcOutput,
+ )
+
+
+class RpcResourceWithRawResponse:
+ def __init__(self, rpc: RpcResource) -> None:
+ self._rpc = rpc
+
+ self.call = to_raw_response_wrapper(
+ rpc.call,
)
class AsyncRpcResourceWithRawResponse:
- """``client.rpc.with_raw_response``."""
+ def __init__(self, rpc: AsyncRpcResource) -> None:
+ self._rpc = rpc
- def __init__(self, resource: AsyncRpcResource) -> None:
- self._resource = resource
- self.call = async_to_raw_response_wrapper(resource.call)
+ self.call = async_to_raw_response_wrapper(
+ rpc.call,
+ )
+
+
+class RpcResourceWithStreamingResponse:
+ def __init__(self, rpc: RpcResource) -> None:
+ self._rpc = rpc
+
+ self.call = to_streamed_response_wrapper(
+ rpc.call,
+ )
class AsyncRpcResourceWithStreamingResponse:
- """``client.rpc.with_streaming_response``."""
+ def __init__(self, rpc: AsyncRpcResource) -> None:
+ self._rpc = rpc
- def __init__(self, resource: AsyncRpcResource) -> None:
- self._resource = resource
- self.call = async_to_streamed_response_wrapper(resource.call)
+ self.call = async_to_streamed_response_wrapper(
+ rpc.call,
+ )
diff --git a/src/opencode/resources/server.py b/src/opencode/resources/server.py
index b2178de..cd43ff8 100644
--- a/src/opencode/resources/server.py
+++ b/src/opencode/resources/server.py
@@ -1,297 +1,270 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``server`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
+import httpx
-from .._base_client import RequestOptions
-from .._qs import encode_path_segment
-from .._resource import AsyncAPIResource, SyncAPIResource
+from .._types import Body, Query, Headers, NotGiven, not_given
+from .._utils import path_template
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- PairingCode,
- PairingSession,
- ServerInfo,
- )
+from .._base_client import make_request_options
+from ..types.shared.server_info import ServerInfo
+from ..types.shared.pairing_code import PairingCode
+from ..types.shared.pairing_session import PairingSession
__all__ = ["ServerResource", "AsyncServerResource"]
class ServerResource(SyncAPIResource):
- """Operations under ``client.server`` (OpenCode ``server``)."""
-
@cached_property
def with_raw_response(self) -> ServerResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return ServerResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> ServerResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return ServerResourceWithStreamingResponse(self)
- def info(
+ def connect(
self,
+ code: str,
*,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> ServerInfo:
- """Get server info.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PairingSession:
+ """
+ Redeem a pairing code. Browsers receive a session cookie and a redirect to the
+ web app; requests that accept JSON receive a session token to use as the
+ password.
+
+ Args:
+ extra_headers: Send extra headers
- Return the server identity, connection URLs, paths, and readiness status.
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
+ extra_body: Add additional JSON properties to the request
- ``GET /api/info``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "ServerInfo",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/info",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ServerInfo",
+ if not code:
+ raise ValueError(f"Expected a non-empty value for `code` but received {code!r}")
+ return self._get(
+ path_template("/auth/connect/{code}", code=code),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=PairingSession,
)
- def pair(
+ def info(
self,
*,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PairingCode:
- """Create pairing code.
-
- Create a short-lived, single-use code for a /auth/connect/:code pairing link.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
-
- ``POST /api/pair``
- """
- return cast(
- "PairingCode",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/pair",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PairingCode",
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ServerInfo:
+ """Return the server identity, connection URLs, paths, and readiness status."""
+ return self._get(
+ "/api/info",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=ServerInfo,
)
- def connect(
+ def pair(
self,
- code: str,
*,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PairingSession:
- """Redeem pairing code.
-
- Redeem a pairing code. Browsers receive a session cookie and a redirect to the web app; requests that accept JSON receive a session token to use as the password.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
-
- ``GET /auth/connect/:code``
- """
- return cast(
- "PairingSession",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/auth/connect/{encode_path_segment(code)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PairingSession",
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PairingCode:
+ """Create a short-lived, single-use code for a /auth/connect/:code pairing link."""
+ return self._post(
+ "/api/pair",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=PairingCode,
)
-class ServerResourceWithRawResponse:
- """``client.server.with_raw_response``."""
-
- def __init__(self, resource: ServerResource) -> None:
- self._resource = resource
- self.info = to_raw_response_wrapper(resource.info)
- self.pair = to_raw_response_wrapper(resource.pair)
- self.connect = to_raw_response_wrapper(resource.connect)
-
-
-class ServerResourceWithStreamingResponse:
- """``client.server.with_streaming_response``."""
-
- def __init__(self, resource: ServerResource) -> None:
- self._resource = resource
- self.info = to_streamed_response_wrapper(resource.info)
- self.pair = to_streamed_response_wrapper(resource.pair)
- self.connect = to_streamed_response_wrapper(resource.connect)
-
-
class AsyncServerResource(AsyncAPIResource):
- """Operations under ``client.server`` (OpenCode ``server``)."""
-
@cached_property
def with_raw_response(self) -> AsyncServerResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncServerResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncServerResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncServerResourceWithStreamingResponse(self)
- async def info(
+ async def connect(
self,
+ code: str,
*,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> ServerInfo:
- """Get server info.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PairingSession:
+ """
+ Redeem a pairing code. Browsers receive a session cookie and a redirect to the
+ web app; requests that accept JSON receive a session token to use as the
+ password.
- Return the server identity, connection URLs, paths, and readiness status.
+ Args:
+ extra_headers: Send extra headers
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
+ extra_query: Add additional query parameters to the request
- ``GET /api/info``
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "ServerInfo",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/info",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ServerInfo",
+ if not code:
+ raise ValueError(f"Expected a non-empty value for `code` but received {code!r}")
+ return await self._get(
+ path_template("/auth/connect/{code}", code=code),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=PairingSession,
)
- async def pair(
+ async def info(
self,
*,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PairingCode:
- """Create pairing code.
-
- Create a short-lived, single-use code for a /auth/connect/:code pairing link.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
-
- ``POST /api/pair``
- """
- return cast(
- "PairingCode",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/pair",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PairingCode",
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ServerInfo:
+ """Return the server identity, connection URLs, paths, and readiness status."""
+ return await self._get(
+ "/api/info",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=ServerInfo,
)
- async def connect(
+ async def pair(
self,
- code: str,
*,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PairingSession:
- """Redeem pairing code.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PairingCode:
+ """Create a short-lived, single-use code for a /auth/connect/:code pairing link."""
+ return await self._post(
+ "/api/pair",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PairingCode,
+ )
- Redeem a pairing code. Browsers receive a session cookie and a redirect to the web app; requests that accept JSON receive a session token to use as the password.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
+class ServerResourceWithRawResponse:
+ def __init__(self, server: ServerResource) -> None:
+ self._server = server
- ``GET /auth/connect/:code``
- """
- return cast(
- "PairingSession",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/auth/connect/{encode_path_segment(code)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PairingSession",
- ),
+ self.connect = to_raw_response_wrapper(
+ server.connect,
+ )
+ self.info = to_raw_response_wrapper(
+ server.info,
+ )
+ self.pair = to_raw_response_wrapper(
+ server.pair,
)
class AsyncServerResourceWithRawResponse:
- """``client.server.with_raw_response``."""
+ def __init__(self, server: AsyncServerResource) -> None:
+ self._server = server
+
+ self.connect = async_to_raw_response_wrapper(
+ server.connect,
+ )
+ self.info = async_to_raw_response_wrapper(
+ server.info,
+ )
+ self.pair = async_to_raw_response_wrapper(
+ server.pair,
+ )
- def __init__(self, resource: AsyncServerResource) -> None:
- self._resource = resource
- self.info = async_to_raw_response_wrapper(resource.info)
- self.pair = async_to_raw_response_wrapper(resource.pair)
- self.connect = async_to_raw_response_wrapper(resource.connect)
+
+class ServerResourceWithStreamingResponse:
+ def __init__(self, server: ServerResource) -> None:
+ self._server = server
+
+ self.connect = to_streamed_response_wrapper(
+ server.connect,
+ )
+ self.info = to_streamed_response_wrapper(
+ server.info,
+ )
+ self.pair = to_streamed_response_wrapper(
+ server.pair,
+ )
class AsyncServerResourceWithStreamingResponse:
- """``client.server.with_streaming_response``."""
+ def __init__(self, server: AsyncServerResource) -> None:
+ self._server = server
- def __init__(self, resource: AsyncServerResource) -> None:
- self._resource = resource
- self.info = async_to_streamed_response_wrapper(resource.info)
- self.pair = async_to_streamed_response_wrapper(resource.pair)
- self.connect = async_to_streamed_response_wrapper(resource.connect)
+ self.connect = async_to_streamed_response_wrapper(
+ server.connect,
+ )
+ self.info = async_to_streamed_response_wrapper(
+ server.info,
+ )
+ self.pair = async_to_streamed_response_wrapper(
+ server.pair,
+ )
diff --git a/src/opencode/resources/session/__init__.py b/src/opencode/resources/session/__init__.py
new file mode 100644
index 0000000..637eaff
--- /dev/null
+++ b/src/opencode/resources/session/__init__.py
@@ -0,0 +1,89 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from .form import (
+ FormResource,
+ AsyncFormResource,
+ FormResourceWithRawResponse,
+ AsyncFormResourceWithRawResponse,
+ FormResourceWithStreamingResponse,
+ AsyncFormResourceWithStreamingResponse,
+)
+from .inbox import (
+ InboxResource,
+ AsyncInboxResource,
+ InboxResourceWithRawResponse,
+ AsyncInboxResourceWithRawResponse,
+ InboxResourceWithStreamingResponse,
+ AsyncInboxResourceWithStreamingResponse,
+)
+from .revert import (
+ RevertResource,
+ AsyncRevertResource,
+ RevertResourceWithRawResponse,
+ AsyncRevertResourceWithRawResponse,
+ RevertResourceWithStreamingResponse,
+ AsyncRevertResourceWithStreamingResponse,
+)
+from .message import (
+ MessageResource,
+ AsyncMessageResource,
+ MessageResourceWithRawResponse,
+ AsyncMessageResourceWithRawResponse,
+ MessageResourceWithStreamingResponse,
+ AsyncMessageResourceWithStreamingResponse,
+)
+from .session import (
+ SessionResource,
+ AsyncSessionResource,
+ SessionResourceWithRawResponse,
+ AsyncSessionResourceWithRawResponse,
+ SessionResourceWithStreamingResponse,
+ AsyncSessionResourceWithStreamingResponse,
+)
+from .instructions import (
+ InstructionsResource,
+ AsyncInstructionsResource,
+ InstructionsResourceWithRawResponse,
+ AsyncInstructionsResourceWithRawResponse,
+ InstructionsResourceWithStreamingResponse,
+ AsyncInstructionsResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "RevertResource",
+ "AsyncRevertResource",
+ "RevertResourceWithRawResponse",
+ "AsyncRevertResourceWithRawResponse",
+ "RevertResourceWithStreamingResponse",
+ "AsyncRevertResourceWithStreamingResponse",
+ "InboxResource",
+ "AsyncInboxResource",
+ "InboxResourceWithRawResponse",
+ "AsyncInboxResourceWithRawResponse",
+ "InboxResourceWithStreamingResponse",
+ "AsyncInboxResourceWithStreamingResponse",
+ "InstructionsResource",
+ "AsyncInstructionsResource",
+ "InstructionsResourceWithRawResponse",
+ "AsyncInstructionsResourceWithRawResponse",
+ "InstructionsResourceWithStreamingResponse",
+ "AsyncInstructionsResourceWithStreamingResponse",
+ "MessageResource",
+ "AsyncMessageResource",
+ "MessageResourceWithRawResponse",
+ "AsyncMessageResourceWithRawResponse",
+ "MessageResourceWithStreamingResponse",
+ "AsyncMessageResourceWithStreamingResponse",
+ "FormResource",
+ "AsyncFormResource",
+ "FormResourceWithRawResponse",
+ "AsyncFormResourceWithRawResponse",
+ "FormResourceWithStreamingResponse",
+ "AsyncFormResourceWithStreamingResponse",
+ "SessionResource",
+ "AsyncSessionResource",
+ "SessionResourceWithRawResponse",
+ "AsyncSessionResourceWithRawResponse",
+ "SessionResourceWithStreamingResponse",
+ "AsyncSessionResourceWithStreamingResponse",
+]
diff --git a/src/opencode/resources/session/form.py b/src/opencode/resources/session/form.py
new file mode 100644
index 0000000..442ae4f
--- /dev/null
+++ b/src/opencode/resources/session/form.py
@@ -0,0 +1,547 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.session import form_reply_params, form_cancel_params, form_create_params
+from ...types.shared.form_id import FormID
+from ...types.shared.form_answer import FormAnswer
+from ...types.shared.form_fields import FormFields
+from ...types.shared.form_metadata import FormMetadata
+from ...types.session.form_get_response import FormGetResponse
+from ...types.session.form_list_response import FormListResponse
+from ...types.session.form_create_response import FormCreateResponse
+
+__all__ = ["FormResource", "AsyncFormResource"]
+
+
+class FormResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> FormResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return FormResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> FormResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return FormResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ session_id: str,
+ *,
+ fields: FormFields,
+ title: str,
+ id: Optional[FormID] | Omit = omit,
+ metadata: FormMetadata | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> FormCreateResponse:
+ """
+ Create a form for a session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._post(
+ path_template("/api/session/{session_id}/form", session_id=session_id),
+ body=maybe_transform(
+ {
+ "fields": fields,
+ "title": title,
+ "id": id,
+ "metadata": metadata,
+ },
+ form_create_params.FormCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=FormCreateResponse,
+ )
+
+ def list(
+ self,
+ session_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> FormListResponse:
+ """
+ Retrieve pending forms for a session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._get(
+ path_template("/api/session/{session_id}/form", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=FormListResponse,
+ )
+
+ def cancel(
+ self,
+ form_id: FormID,
+ *,
+ session_id: str,
+ message: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Cancel a pending form, optionally telling the asker why it was not answered.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not form_id:
+ raise ValueError(f"Expected a non-empty value for `form_id` but received {form_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template("/api/session/{session_id}/form/{form_id}", session_id=session_id, form_id=form_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"message": message}, form_cancel_params.FormCancelParams),
+ ),
+ cast_to=NoneType,
+ )
+
+ def get(
+ self,
+ form_id: FormID,
+ *,
+ session_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> FormGetResponse:
+ """
+ Retrieve a form and its current state for a session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not form_id:
+ raise ValueError(f"Expected a non-empty value for `form_id` but received {form_id!r}")
+ return self._get(
+ path_template("/api/session/{session_id}/form/{form_id}", session_id=session_id, form_id=form_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=FormGetResponse,
+ )
+
+ def reply(
+ self,
+ form_id: FormID,
+ *,
+ session_id: str,
+ answer: FormAnswer,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Submit an answer to a pending form.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not form_id:
+ raise ValueError(f"Expected a non-empty value for `form_id` but received {form_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template("/api/session/{session_id}/form/{form_id}/reply", session_id=session_id, form_id=form_id),
+ body=maybe_transform({"answer": answer}, form_reply_params.FormReplyParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncFormResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncFormResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncFormResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncFormResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncFormResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ session_id: str,
+ *,
+ fields: FormFields,
+ title: str,
+ id: Optional[FormID] | Omit = omit,
+ metadata: FormMetadata | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> FormCreateResponse:
+ """
+ Create a form for a session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._post(
+ path_template("/api/session/{session_id}/form", session_id=session_id),
+ body=await async_maybe_transform(
+ {
+ "fields": fields,
+ "title": title,
+ "id": id,
+ "metadata": metadata,
+ },
+ form_create_params.FormCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=FormCreateResponse,
+ )
+
+ async def list(
+ self,
+ session_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> FormListResponse:
+ """
+ Retrieve pending forms for a session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._get(
+ path_template("/api/session/{session_id}/form", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=FormListResponse,
+ )
+
+ async def cancel(
+ self,
+ form_id: FormID,
+ *,
+ session_id: str,
+ message: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Cancel a pending form, optionally telling the asker why it was not answered.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not form_id:
+ raise ValueError(f"Expected a non-empty value for `form_id` but received {form_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template("/api/session/{session_id}/form/{form_id}", session_id=session_id, form_id=form_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"message": message}, form_cancel_params.FormCancelParams),
+ ),
+ cast_to=NoneType,
+ )
+
+ async def get(
+ self,
+ form_id: FormID,
+ *,
+ session_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> FormGetResponse:
+ """
+ Retrieve a form and its current state for a session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not form_id:
+ raise ValueError(f"Expected a non-empty value for `form_id` but received {form_id!r}")
+ return await self._get(
+ path_template("/api/session/{session_id}/form/{form_id}", session_id=session_id, form_id=form_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=FormGetResponse,
+ )
+
+ async def reply(
+ self,
+ form_id: FormID,
+ *,
+ session_id: str,
+ answer: FormAnswer,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Submit an answer to a pending form.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not form_id:
+ raise ValueError(f"Expected a non-empty value for `form_id` but received {form_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template("/api/session/{session_id}/form/{form_id}/reply", session_id=session_id, form_id=form_id),
+ body=await async_maybe_transform({"answer": answer}, form_reply_params.FormReplyParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class FormResourceWithRawResponse:
+ def __init__(self, form: FormResource) -> None:
+ self._form = form
+
+ self.create = to_raw_response_wrapper(
+ form.create,
+ )
+ self.list = to_raw_response_wrapper(
+ form.list,
+ )
+ self.cancel = to_raw_response_wrapper(
+ form.cancel,
+ )
+ self.get = to_raw_response_wrapper(
+ form.get,
+ )
+ self.reply = to_raw_response_wrapper(
+ form.reply,
+ )
+
+
+class AsyncFormResourceWithRawResponse:
+ def __init__(self, form: AsyncFormResource) -> None:
+ self._form = form
+
+ self.create = async_to_raw_response_wrapper(
+ form.create,
+ )
+ self.list = async_to_raw_response_wrapper(
+ form.list,
+ )
+ self.cancel = async_to_raw_response_wrapper(
+ form.cancel,
+ )
+ self.get = async_to_raw_response_wrapper(
+ form.get,
+ )
+ self.reply = async_to_raw_response_wrapper(
+ form.reply,
+ )
+
+
+class FormResourceWithStreamingResponse:
+ def __init__(self, form: FormResource) -> None:
+ self._form = form
+
+ self.create = to_streamed_response_wrapper(
+ form.create,
+ )
+ self.list = to_streamed_response_wrapper(
+ form.list,
+ )
+ self.cancel = to_streamed_response_wrapper(
+ form.cancel,
+ )
+ self.get = to_streamed_response_wrapper(
+ form.get,
+ )
+ self.reply = to_streamed_response_wrapper(
+ form.reply,
+ )
+
+
+class AsyncFormResourceWithStreamingResponse:
+ def __init__(self, form: AsyncFormResource) -> None:
+ self._form = form
+
+ self.create = async_to_streamed_response_wrapper(
+ form.create,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ form.list,
+ )
+ self.cancel = async_to_streamed_response_wrapper(
+ form.cancel,
+ )
+ self.get = async_to_streamed_response_wrapper(
+ form.get,
+ )
+ self.reply = async_to_streamed_response_wrapper(
+ form.reply,
+ )
diff --git a/src/opencode/resources/session/inbox.py b/src/opencode/resources/session/inbox.py
new file mode 100644
index 0000000..63262b7
--- /dev/null
+++ b/src/opencode/resources/session/inbox.py
@@ -0,0 +1,348 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import Body, Query, Headers, NoneType, NotGiven, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.session import inbox_update_params
+from ...types.shared.session_id import SessionID
+from ...types.shared.session_message_id import SessionMessageID
+from ...types.session.inbox_list_response import InboxListResponse
+from ...types.shared.session_inbox_delivery import SessionInboxDelivery
+
+__all__ = ["InboxResource", "AsyncInboxResource"]
+
+
+class InboxResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> InboxResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return InboxResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> InboxResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return InboxResourceWithStreamingResponse(self)
+
+ def update(
+ self,
+ inbox_id: SessionMessageID,
+ *,
+ session_id: SessionID,
+ delivery: SessionInboxDelivery,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Change a pending inbox item's delivery mode. Steering wakes session execution.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not inbox_id:
+ raise ValueError(f"Expected a non-empty value for `inbox_id` but received {inbox_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._patch(
+ path_template("/api/session/{session_id}/inbox/{inbox_id}", session_id=session_id, inbox_id=inbox_id),
+ body=maybe_transform({"delivery": delivery}, inbox_update_params.InboxUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def list(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> InboxListResponse:
+ """
+ List durable enqueued session work not yet delivered, ordered by enqueue
+ sequence. Includes user, synthetic, compaction, and move items.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._get(
+ path_template("/api/session/{session_id}/inbox", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=InboxListResponse,
+ )
+
+ def cancel(
+ self,
+ inbox_id: SessionMessageID,
+ *,
+ session_id: SessionID,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Cancel an inbox item that has not yet been delivered. Unavailable items are a
+ no-op.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not inbox_id:
+ raise ValueError(f"Expected a non-empty value for `inbox_id` but received {inbox_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template("/api/session/{session_id}/inbox/{inbox_id}", session_id=session_id, inbox_id=inbox_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncInboxResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncInboxResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncInboxResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncInboxResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncInboxResourceWithStreamingResponse(self)
+
+ async def update(
+ self,
+ inbox_id: SessionMessageID,
+ *,
+ session_id: SessionID,
+ delivery: SessionInboxDelivery,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Change a pending inbox item's delivery mode. Steering wakes session execution.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not inbox_id:
+ raise ValueError(f"Expected a non-empty value for `inbox_id` but received {inbox_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._patch(
+ path_template("/api/session/{session_id}/inbox/{inbox_id}", session_id=session_id, inbox_id=inbox_id),
+ body=await async_maybe_transform({"delivery": delivery}, inbox_update_params.InboxUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def list(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> InboxListResponse:
+ """
+ List durable enqueued session work not yet delivered, ordered by enqueue
+ sequence. Includes user, synthetic, compaction, and move items.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._get(
+ path_template("/api/session/{session_id}/inbox", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=InboxListResponse,
+ )
+
+ async def cancel(
+ self,
+ inbox_id: SessionMessageID,
+ *,
+ session_id: SessionID,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Cancel an inbox item that has not yet been delivered. Unavailable items are a
+ no-op.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not inbox_id:
+ raise ValueError(f"Expected a non-empty value for `inbox_id` but received {inbox_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template("/api/session/{session_id}/inbox/{inbox_id}", session_id=session_id, inbox_id=inbox_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class InboxResourceWithRawResponse:
+ def __init__(self, inbox: InboxResource) -> None:
+ self._inbox = inbox
+
+ self.update = to_raw_response_wrapper(
+ inbox.update,
+ )
+ self.list = to_raw_response_wrapper(
+ inbox.list,
+ )
+ self.cancel = to_raw_response_wrapper(
+ inbox.cancel,
+ )
+
+
+class AsyncInboxResourceWithRawResponse:
+ def __init__(self, inbox: AsyncInboxResource) -> None:
+ self._inbox = inbox
+
+ self.update = async_to_raw_response_wrapper(
+ inbox.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ inbox.list,
+ )
+ self.cancel = async_to_raw_response_wrapper(
+ inbox.cancel,
+ )
+
+
+class InboxResourceWithStreamingResponse:
+ def __init__(self, inbox: InboxResource) -> None:
+ self._inbox = inbox
+
+ self.update = to_streamed_response_wrapper(
+ inbox.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ inbox.list,
+ )
+ self.cancel = to_streamed_response_wrapper(
+ inbox.cancel,
+ )
+
+
+class AsyncInboxResourceWithStreamingResponse:
+ def __init__(self, inbox: AsyncInboxResource) -> None:
+ self._inbox = inbox
+
+ self.update = async_to_streamed_response_wrapper(
+ inbox.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ inbox.list,
+ )
+ self.cancel = async_to_streamed_response_wrapper(
+ inbox.cancel,
+ )
diff --git a/src/opencode/resources/session/instructions/__init__.py b/src/opencode/resources/session/instructions/__init__.py
new file mode 100644
index 0000000..3de26a4
--- /dev/null
+++ b/src/opencode/resources/session/instructions/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from .entry import (
+ EntryResource,
+ AsyncEntryResource,
+ EntryResourceWithRawResponse,
+ AsyncEntryResourceWithRawResponse,
+ EntryResourceWithStreamingResponse,
+ AsyncEntryResourceWithStreamingResponse,
+)
+from .instructions import (
+ InstructionsResource,
+ AsyncInstructionsResource,
+ InstructionsResourceWithRawResponse,
+ AsyncInstructionsResourceWithRawResponse,
+ InstructionsResourceWithStreamingResponse,
+ AsyncInstructionsResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "EntryResource",
+ "AsyncEntryResource",
+ "EntryResourceWithRawResponse",
+ "AsyncEntryResourceWithRawResponse",
+ "EntryResourceWithStreamingResponse",
+ "AsyncEntryResourceWithStreamingResponse",
+ "InstructionsResource",
+ "AsyncInstructionsResource",
+ "InstructionsResourceWithRawResponse",
+ "AsyncInstructionsResourceWithRawResponse",
+ "InstructionsResourceWithStreamingResponse",
+ "AsyncInstructionsResourceWithStreamingResponse",
+]
diff --git a/src/opencode/resources/session/instructions/entry.py b/src/opencode/resources/session/instructions/entry.py
new file mode 100644
index 0000000..5a2d133
--- /dev/null
+++ b/src/opencode/resources/session/instructions/entry.py
@@ -0,0 +1,355 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ...._types import Body, Query, Headers, NoneType, NotGiven, not_given
+from ...._utils import path_template, maybe_transform, async_maybe_transform
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...._base_client import make_request_options
+from ....types.shared.session_id import SessionID
+from ....types.session.instructions import entry_put_params
+from ....types.shared.instruction_entry_key import InstructionEntryKey
+from ....types.session.instructions.entry_list_response import EntryListResponse
+
+__all__ = ["EntryResource", "AsyncEntryResource"]
+
+
+class EntryResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> EntryResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return EntryResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> EntryResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return EntryResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> EntryListResponse:
+ """
+ List API-managed instruction entries attached to the session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._get(
+ path_template("/api/experimental/session/{session_id}/instructions/entries", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=EntryListResponse,
+ )
+
+ def put(
+ self,
+ key: InstructionEntryKey,
+ *,
+ session_id: SessionID,
+ value: object,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Attach or replace one durable instruction entry. Changes announce as updates at
+ the next step boundary.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not key:
+ raise ValueError(f"Expected a non-empty value for `key` but received {key!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._put(
+ path_template(
+ "/api/experimental/session/{session_id}/instructions/entries/{key}", session_id=session_id, key=key
+ ),
+ body=maybe_transform({"value": value}, entry_put_params.EntryPutParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def remove(
+ self,
+ key: InstructionEntryKey,
+ *,
+ session_id: SessionID,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Remove one instruction entry; the removal is announced to the model at the next
+ step boundary.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not key:
+ raise ValueError(f"Expected a non-empty value for `key` but received {key!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template(
+ "/api/experimental/session/{session_id}/instructions/entries/{key}", session_id=session_id, key=key
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncEntryResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncEntryResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncEntryResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncEntryResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncEntryResourceWithStreamingResponse(self)
+
+ async def list(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> EntryListResponse:
+ """
+ List API-managed instruction entries attached to the session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._get(
+ path_template("/api/experimental/session/{session_id}/instructions/entries", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=EntryListResponse,
+ )
+
+ async def put(
+ self,
+ key: InstructionEntryKey,
+ *,
+ session_id: SessionID,
+ value: object,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Attach or replace one durable instruction entry. Changes announce as updates at
+ the next step boundary.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not key:
+ raise ValueError(f"Expected a non-empty value for `key` but received {key!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._put(
+ path_template(
+ "/api/experimental/session/{session_id}/instructions/entries/{key}", session_id=session_id, key=key
+ ),
+ body=await async_maybe_transform({"value": value}, entry_put_params.EntryPutParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def remove(
+ self,
+ key: InstructionEntryKey,
+ *,
+ session_id: SessionID,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Remove one instruction entry; the removal is announced to the model at the next
+ step boundary.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not key:
+ raise ValueError(f"Expected a non-empty value for `key` but received {key!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template(
+ "/api/experimental/session/{session_id}/instructions/entries/{key}", session_id=session_id, key=key
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class EntryResourceWithRawResponse:
+ def __init__(self, entry: EntryResource) -> None:
+ self._entry = entry
+
+ self.list = to_raw_response_wrapper(
+ entry.list,
+ )
+ self.put = to_raw_response_wrapper(
+ entry.put,
+ )
+ self.remove = to_raw_response_wrapper(
+ entry.remove,
+ )
+
+
+class AsyncEntryResourceWithRawResponse:
+ def __init__(self, entry: AsyncEntryResource) -> None:
+ self._entry = entry
+
+ self.list = async_to_raw_response_wrapper(
+ entry.list,
+ )
+ self.put = async_to_raw_response_wrapper(
+ entry.put,
+ )
+ self.remove = async_to_raw_response_wrapper(
+ entry.remove,
+ )
+
+
+class EntryResourceWithStreamingResponse:
+ def __init__(self, entry: EntryResource) -> None:
+ self._entry = entry
+
+ self.list = to_streamed_response_wrapper(
+ entry.list,
+ )
+ self.put = to_streamed_response_wrapper(
+ entry.put,
+ )
+ self.remove = to_streamed_response_wrapper(
+ entry.remove,
+ )
+
+
+class AsyncEntryResourceWithStreamingResponse:
+ def __init__(self, entry: AsyncEntryResource) -> None:
+ self._entry = entry
+
+ self.list = async_to_streamed_response_wrapper(
+ entry.list,
+ )
+ self.put = async_to_streamed_response_wrapper(
+ entry.put,
+ )
+ self.remove = async_to_streamed_response_wrapper(
+ entry.remove,
+ )
diff --git a/src/opencode/resources/session/instructions/instructions.py b/src/opencode/resources/session/instructions/instructions.py
new file mode 100644
index 0000000..a29bb45
--- /dev/null
+++ b/src/opencode/resources/session/instructions/instructions.py
@@ -0,0 +1,105 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from .entry import (
+ EntryResource,
+ AsyncEntryResource,
+ EntryResourceWithRawResponse,
+ AsyncEntryResourceWithRawResponse,
+ EntryResourceWithStreamingResponse,
+ AsyncEntryResourceWithStreamingResponse,
+)
+from ...._types import Body, Query, Headers, NotGiven, not_given
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+
+__all__ = ["InstructionsResource", "AsyncInstructionsResource"]
+
+
+class InstructionsResource(SyncAPIResource):
+ @cached_property
+ def entry(self) -> EntryResource:
+ return EntryResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> InstructionsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return InstructionsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> InstructionsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return InstructionsResourceWithStreamingResponse(self)
+
+
+class AsyncInstructionsResource(AsyncAPIResource):
+ @cached_property
+ def entry(self) -> AsyncEntryResource:
+ return AsyncEntryResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncInstructionsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncInstructionsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncInstructionsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncInstructionsResourceWithStreamingResponse(self)
+
+
+class InstructionsResourceWithRawResponse:
+ def __init__(self, instructions: InstructionsResource) -> None:
+ self._instructions = instructions
+
+ @cached_property
+ def entry(self) -> EntryResourceWithRawResponse:
+ return EntryResourceWithRawResponse(self._instructions.entry)
+
+
+class AsyncInstructionsResourceWithRawResponse:
+ def __init__(self, instructions: AsyncInstructionsResource) -> None:
+ self._instructions = instructions
+
+ @cached_property
+ def entry(self) -> AsyncEntryResourceWithRawResponse:
+ return AsyncEntryResourceWithRawResponse(self._instructions.entry)
+
+
+class InstructionsResourceWithStreamingResponse:
+ def __init__(self, instructions: InstructionsResource) -> None:
+ self._instructions = instructions
+
+ @cached_property
+ def entry(self) -> EntryResourceWithStreamingResponse:
+ return EntryResourceWithStreamingResponse(self._instructions.entry)
+
+
+class AsyncInstructionsResourceWithStreamingResponse:
+ def __init__(self, instructions: AsyncInstructionsResource) -> None:
+ self._instructions = instructions
+
+ @cached_property
+ def entry(self) -> AsyncEntryResourceWithStreamingResponse:
+ return AsyncEntryResourceWithStreamingResponse(self._instructions.entry)
diff --git a/src/opencode/resources/session/message.py b/src/opencode/resources/session/message.py
new file mode 100644
index 0000000..7920a2b
--- /dev/null
+++ b/src/opencode/resources/session/message.py
@@ -0,0 +1,176 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import Body, Query, Headers, NotGiven, not_given
+from ..._utils import path_template
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.shared.session_id import SessionID
+from ...types.shared.session_message_id import SessionMessageID
+from ...types.session.message_get_response import MessageGetResponse
+
+__all__ = ["MessageResource", "AsyncMessageResource"]
+
+
+class MessageResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> MessageResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return MessageResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> MessageResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return MessageResourceWithStreamingResponse(self)
+
+ def get(
+ self,
+ message_id: SessionMessageID,
+ *,
+ session_id: SessionID,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> MessageGetResponse:
+ """
+ Retrieve one projected message owned by the Session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not message_id:
+ raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}")
+ return self._get(
+ path_template(
+ "/api/session/{session_id}/message/{message_id}", session_id=session_id, message_id=message_id
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=MessageGetResponse,
+ )
+
+
+class AsyncMessageResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncMessageResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncMessageResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncMessageResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncMessageResourceWithStreamingResponse(self)
+
+ async def get(
+ self,
+ message_id: SessionMessageID,
+ *,
+ session_id: SessionID,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> MessageGetResponse:
+ """
+ Retrieve one projected message owned by the Session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ if not message_id:
+ raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}")
+ return await self._get(
+ path_template(
+ "/api/session/{session_id}/message/{message_id}", session_id=session_id, message_id=message_id
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=MessageGetResponse,
+ )
+
+
+class MessageResourceWithRawResponse:
+ def __init__(self, message: MessageResource) -> None:
+ self._message = message
+
+ self.get = to_raw_response_wrapper(
+ message.get,
+ )
+
+
+class AsyncMessageResourceWithRawResponse:
+ def __init__(self, message: AsyncMessageResource) -> None:
+ self._message = message
+
+ self.get = async_to_raw_response_wrapper(
+ message.get,
+ )
+
+
+class MessageResourceWithStreamingResponse:
+ def __init__(self, message: MessageResource) -> None:
+ self._message = message
+
+ self.get = to_streamed_response_wrapper(
+ message.get,
+ )
+
+
+class AsyncMessageResourceWithStreamingResponse:
+ def __init__(self, message: AsyncMessageResource) -> None:
+ self._message = message
+
+ self.get = async_to_streamed_response_wrapper(
+ message.get,
+ )
diff --git a/src/opencode/resources/session/revert.py b/src/opencode/resources/session/revert.py
new file mode 100644
index 0000000..7ba0f4f
--- /dev/null
+++ b/src/opencode/resources/session/revert.py
@@ -0,0 +1,349 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.session import revert_stage_params
+from ...types.shared.session_id import SessionID
+from ...types.shared.session_message_id import SessionMessageID
+from ...types.session.revert_stage_response import RevertStageResponse
+
+__all__ = ["RevertResource", "AsyncRevertResource"]
+
+
+class RevertResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> RevertResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return RevertResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> RevertResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return RevertResourceWithStreamingResponse(self)
+
+ def clear(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Clear staged revert
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template("/api/session/{session_id}/revert", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def commit(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Commit staged revert
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template("/api/session/{session_id}/revert/commit", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def stage(
+ self,
+ session_id: SessionID,
+ *,
+ message_id: SessionMessageID,
+ files: Optional[bool] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> RevertStageResponse:
+ """
+ Stage or move a reversible session boundary and optionally apply its file
+ changes.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._post(
+ path_template("/api/session/{session_id}/revert/stage", session_id=session_id),
+ body=maybe_transform(
+ {
+ "message_id": message_id,
+ "files": files,
+ },
+ revert_stage_params.RevertStageParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=RevertStageResponse,
+ )
+
+
+class AsyncRevertResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncRevertResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncRevertResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncRevertResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncRevertResourceWithStreamingResponse(self)
+
+ async def clear(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Clear staged revert
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template("/api/session/{session_id}/revert", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def commit(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Commit staged revert
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template("/api/session/{session_id}/revert/commit", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def stage(
+ self,
+ session_id: SessionID,
+ *,
+ message_id: SessionMessageID,
+ files: Optional[bool] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> RevertStageResponse:
+ """
+ Stage or move a reversible session boundary and optionally apply its file
+ changes.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._post(
+ path_template("/api/session/{session_id}/revert/stage", session_id=session_id),
+ body=await async_maybe_transform(
+ {
+ "message_id": message_id,
+ "files": files,
+ },
+ revert_stage_params.RevertStageParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=RevertStageResponse,
+ )
+
+
+class RevertResourceWithRawResponse:
+ def __init__(self, revert: RevertResource) -> None:
+ self._revert = revert
+
+ self.clear = to_raw_response_wrapper(
+ revert.clear,
+ )
+ self.commit = to_raw_response_wrapper(
+ revert.commit,
+ )
+ self.stage = to_raw_response_wrapper(
+ revert.stage,
+ )
+
+
+class AsyncRevertResourceWithRawResponse:
+ def __init__(self, revert: AsyncRevertResource) -> None:
+ self._revert = revert
+
+ self.clear = async_to_raw_response_wrapper(
+ revert.clear,
+ )
+ self.commit = async_to_raw_response_wrapper(
+ revert.commit,
+ )
+ self.stage = async_to_raw_response_wrapper(
+ revert.stage,
+ )
+
+
+class RevertResourceWithStreamingResponse:
+ def __init__(self, revert: RevertResource) -> None:
+ self._revert = revert
+
+ self.clear = to_streamed_response_wrapper(
+ revert.clear,
+ )
+ self.commit = to_streamed_response_wrapper(
+ revert.commit,
+ )
+ self.stage = to_streamed_response_wrapper(
+ revert.stage,
+ )
+
+
+class AsyncRevertResourceWithStreamingResponse:
+ def __init__(self, revert: AsyncRevertResource) -> None:
+ self._revert = revert
+
+ self.clear = async_to_streamed_response_wrapper(
+ revert.clear,
+ )
+ self.commit = async_to_streamed_response_wrapper(
+ revert.commit,
+ )
+ self.stage = async_to_streamed_response_wrapper(
+ revert.stage,
+ )
diff --git a/src/opencode/resources/session/session.py b/src/opencode/resources/session/session.py
new file mode 100644
index 0000000..ec3ab3c
--- /dev/null
+++ b/src/opencode/resources/session/session.py
@@ -0,0 +1,3011 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Iterable, Optional
+from typing_extensions import Literal
+
+import httpx
+
+from .form import (
+ FormResource,
+ AsyncFormResource,
+ FormResourceWithRawResponse,
+ AsyncFormResourceWithRawResponse,
+ FormResourceWithStreamingResponse,
+ AsyncFormResourceWithStreamingResponse,
+)
+from .inbox import (
+ InboxResource,
+ AsyncInboxResource,
+ InboxResourceWithRawResponse,
+ AsyncInboxResourceWithRawResponse,
+ InboxResourceWithStreamingResponse,
+ AsyncInboxResourceWithStreamingResponse,
+)
+from .revert import (
+ RevertResource,
+ AsyncRevertResource,
+ RevertResourceWithRawResponse,
+ AsyncRevertResourceWithRawResponse,
+ RevertResourceWithStreamingResponse,
+ AsyncRevertResourceWithStreamingResponse,
+)
+from ...types import (
+ session_log_params,
+ session_diff_params,
+ session_fork_params,
+ session_list_params,
+ session_move_params,
+ session_view_params,
+ session_shell_params,
+ session_skill_params,
+ session_stats_params,
+ session_create_params,
+ session_export_params,
+ session_prompt_params,
+ session_update_params,
+ session_command_params,
+ session_compact_params,
+ session_import__params,
+ session_generate_params,
+ session_interrupt_params,
+ session_synthetic_params,
+ session_environment_params,
+ session_switch_agent_params,
+ session_switch_model_params,
+)
+from .message import (
+ MessageResource,
+ AsyncMessageResource,
+ MessageResourceWithRawResponse,
+ AsyncMessageResourceWithRawResponse,
+ MessageResourceWithStreamingResponse,
+ AsyncMessageResourceWithStreamingResponse,
+)
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...pagination import SyncCursorPage, AsyncCursorPage
+from ..._base_client import AsyncPaginator, make_request_options
+from ...types.shared.agent_id import AgentID
+from ...types.shared.skill_id import SkillID
+from ...types.shared.event_seq import EventSeq
+from ...types.shared.model_ref import ModelRef
+from ...types.shared.project_id import ProjectID
+from ...types.shared.session_id import SessionID
+from .instructions.instructions import (
+ InstructionsResource,
+ AsyncInstructionsResource,
+ InstructionsResourceWithRawResponse,
+ AsyncInstructionsResourceWithRawResponse,
+ InstructionsResourceWithStreamingResponse,
+ AsyncInstructionsResourceWithStreamingResponse,
+)
+from ...types.shared.session_info import SessionInfo
+from ...types.session_get_response import SessionGetResponse
+from ...types.shared.absolute_path import AbsolutePath
+from ...types.shared.relative_path import RelativePath
+from ...types.session_diff_response import SessionDiffResponse
+from ...types.session_fork_response import SessionForkResponse
+from ...types.session_stats_response import SessionStatsResponse
+from ...types.shared.sessions_cursor import SessionsCursor
+from ...types.session_active_response import SessionActiveResponse
+from ...types.session_create_response import SessionCreateResponse
+from ...types.session_export_response import SessionExportResponse
+from ...types.session_prompt_response import SessionPromptResponse
+from ...types.shared.session_metadata import SessionMetadata
+from ...types.session_compact_response import SessionCompactResponse
+from ...types.session_context_response import SessionContextResponse
+from ...types.session_import__response import SessionImportResponse
+from ...types.shared.permission_ruleset import PermissionRuleset
+from ...types.shared.session_message_id import SessionMessageID
+from ...types.session_synthetic_response import SessionSyntheticResponse
+from ...types.shared.location_public_ref import LocationPublicRef
+from ...types.shared.session_inbox_delivery import SessionInboxDelivery
+from ...types.shared.prompt_agent_attachment import PromptAgentAttachment
+from ...types.shared.session_generate_response import SessionGenerateResponse
+from ...types.shared.session_interrupt_response import SessionInterruptResponse
+from ...types.shared.prompt_input_file_attachment import PromptInputFileAttachment
+from ...types.shared.prompt_input_skill_attachment import PromptInputSkillAttachment
+
+__all__ = ["SessionResource", "AsyncSessionResource"]
+
+
+class SessionResource(SyncAPIResource):
+ @cached_property
+ def revert(self) -> RevertResource:
+ return RevertResource(self._client)
+
+ @cached_property
+ def inbox(self) -> InboxResource:
+ return InboxResource(self._client)
+
+ @cached_property
+ def instructions(self) -> InstructionsResource:
+ return InstructionsResource(self._client)
+
+ @cached_property
+ def message(self) -> MessageResource:
+ return MessageResource(self._client)
+
+ @cached_property
+ def form(self) -> FormResource:
+ return FormResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> SessionResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return SessionResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> SessionResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return SessionResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ id: Optional[SessionID] | Omit = omit,
+ agent: Optional[AgentID] | Omit = omit,
+ location: Optional[LocationPublicRef] | Omit = omit,
+ metadata: Optional[SessionMetadata] | Omit = omit,
+ model: Optional[ModelRef] | Omit = omit,
+ parent_id: Optional[SessionID] | Omit = omit,
+ permissions: Optional[PermissionRuleset] | Omit = omit,
+ title: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionCreateResponse:
+ """
+ Create a session at the requested location. A parentID creates a linked child
+ session at its parent's location.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/api/session",
+ body=maybe_transform(
+ {
+ "id": id,
+ "agent": agent,
+ "location": location,
+ "metadata": metadata,
+ "model": model,
+ "parent_id": parent_id,
+ "permissions": permissions,
+ "title": title,
+ },
+ session_create_params.SessionCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionCreateResponse,
+ )
+
+ def update(
+ self,
+ session_id: SessionID,
+ *,
+ archived: Optional[bool] | Omit = omit,
+ metadata: Optional[SessionMetadata] | Omit = omit,
+ permissions: Optional[PermissionRuleset] | Omit = omit,
+ title: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Update mutable session properties.
+
+ Args:
+ archived: Archive the session with true or restore it with false. Repeating the current
+ state is a no-op.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._patch(
+ path_template("/api/session/{session_id}", session_id=session_id),
+ body=maybe_transform(
+ {
+ "archived": archived,
+ "metadata": metadata,
+ "permissions": permissions,
+ "title": title,
+ },
+ session_update_params.SessionUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def list(
+ self,
+ *,
+ archived: Optional[bool] | Omit = omit,
+ cursor: Optional[SessionsCursor] | Omit = omit,
+ directory: Optional[AbsolutePath] | Omit = omit,
+ limit: Optional[int] | Omit = omit,
+ order: Optional[Literal["asc", "desc"]] | Omit = omit,
+ parent_id: Optional[SessionID] | Omit = omit,
+ project: Optional[ProjectID] | Omit = omit,
+ search: Optional[str] | Omit = omit,
+ subpath: Optional[RelativePath] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SyncCursorPage[SessionInfo]:
+ """
+ Retrieve sessions in the requested order. Items keep that order across pages;
+ use cursor.next or cursor.previous to move through the ordered list.
+
+ Args:
+ limit: Maximum number of sessions to return. Defaults to the newest 50 sessions.
+
+ order: Session order for the first page. Use desc for newest first or asc for oldest
+ first.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get_api_list(
+ "/api/session",
+ page=SyncCursorPage[SessionInfo],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "archived": archived,
+ "cursor": cursor,
+ "directory": directory,
+ "limit": limit,
+ "order": order,
+ "parent_id": parent_id,
+ "project": project,
+ "search": search,
+ "subpath": subpath,
+ },
+ session_list_params.SessionListParams,
+ ),
+ ),
+ model=SessionInfo,
+ )
+
+ def active(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionActiveResponse:
+ """
+ Retrieve foreground Session drains currently owned by this OpenCode process.
+ Sessions absent from the result are inactive.
+ """
+ return self._get(
+ "/api/session/active",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionActiveResponse,
+ )
+
+ def background(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Move active foreground backgroundable tools for this session into background
+ observation. Idle requests are a no-op.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template("/api/session/{session_id}/background", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def command(
+ self,
+ session_id: SessionID,
+ *,
+ name: str,
+ text: str,
+ agents: Iterable[PromptAgentAttachment] | Omit = omit,
+ delivery: Optional[SessionInboxDelivery] | Omit = omit,
+ files: Iterable[PromptInputFileAttachment] | Omit = omit,
+ skills: Iterable[PromptInputSkillAttachment] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Execute a slash command callback immediately.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template("/api/session/{session_id}/command", session_id=session_id),
+ body=maybe_transform(
+ {
+ "name": name,
+ "text": text,
+ "agents": agents,
+ "delivery": delivery,
+ "files": files,
+ "skills": skills,
+ },
+ session_command_params.SessionCommandParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def compact(
+ self,
+ session_id: SessionID,
+ *,
+ id: Optional[SessionMessageID] | Omit = omit,
+ delivery: Optional[SessionInboxDelivery] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionCompactResponse:
+ """
+ Durably admit a session compaction request. Steers by default: it runs at the
+ next step boundary instead of waiting behind queued prompts.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._post(
+ path_template("/api/session/{session_id}/compact", session_id=session_id),
+ body=maybe_transform(
+ {
+ "id": id,
+ "delivery": delivery,
+ },
+ session_compact_params.SessionCompactParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionCompactResponse,
+ )
+
+ def context(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionContextResponse:
+ """
+ Retrieve the active context messages for a session (all messages after the last
+ compaction).
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._get(
+ path_template("/api/session/{session_id}/context", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionContextResponse,
+ )
+
+ def diff(
+ self,
+ session_id: SessionID,
+ *,
+ context: Optional[int] | Omit = omit,
+ from_: Optional[SessionMessageID] | Omit = omit,
+ to: Optional[SessionMessageID] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionDiffResponse:
+ """
+ Structured per-file diffs of the files a turn changed. A turn runs from the
+ first prompt after the session was last idle until its next idle marker, so
+ prompts steered in while it was busy belong to the same turn; `to` extends the
+ range through a later turn. Compares the range's first recorded snapshot with
+ its last; a step still running in the active session compares against the
+ working copy. Ranges that span a location change are rejected. In sessions
+ without any idle marker, a prompt's turn spans until the next user message.
+
+ Args:
+ context: Unchanged lines around each hunk. Omit for full-file patches.
+
+ from_: User message whose turn to diff. Defaults to the turn of the newest user
+ message.
+
+ to: Later user message whose turn ends the range. Defaults to the turn of `from`
+ alone.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._get(
+ path_template("/api/session/{session_id}/diff", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "context": context,
+ "from_": from_,
+ "to": to,
+ },
+ session_diff_params.SessionDiffParams,
+ ),
+ ),
+ cast_to=SessionDiffResponse,
+ )
+
+ def environment(
+ self,
+ session_id: SessionID,
+ *,
+ variables: Dict[str, str],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Replace the process environment used by local shell commands for this session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._put(
+ path_template("/api/session/{session_id}/environment", session_id=session_id),
+ body=maybe_transform({"variables": variables}, session_environment_params.SessionEnvironmentParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def export(
+ self,
+ session_id: SessionID,
+ *,
+ sanitize: Optional[bool] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionExportResponse:
+ """
+ Export a complete projected session transcript.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._get(
+ path_template("/api/experimental/session/{session_id}/export", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"sanitize": sanitize}, session_export_params.SessionExportParams),
+ ),
+ cast_to=SessionExportResponse,
+ )
+
+ def fork(
+ self,
+ session_id: SessionID,
+ *,
+ before: Optional[SessionMessageID] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionForkResponse:
+ """
+ Create a child session by copying projected history before a message. Omit
+ before to copy the full history.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._post(
+ path_template("/api/session/{session_id}/fork", session_id=session_id),
+ body=maybe_transform({"before": before}, session_fork_params.SessionForkParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionForkResponse,
+ )
+
+ def generate(
+ self,
+ session_id: SessionID,
+ *,
+ prompt: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionGenerateResponse:
+ """
+ Generate transient text from the current session context without mutating
+ session history.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._post(
+ path_template("/api/session/{session_id}/generate", session_id=session_id),
+ body=maybe_transform({"prompt": prompt}, session_generate_params.SessionGenerateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionGenerateResponse,
+ )
+
+ def get(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionGetResponse:
+ """
+ Retrieve a session by ID.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._get(
+ path_template("/api/session/{session_id}", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionGetResponse,
+ )
+
+ def import_(
+ self,
+ *,
+ info: SessionInfo,
+ messages: Iterable[object],
+ location: Optional[LocationPublicRef] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionImportResponse:
+ """
+ Import a projected session transcript at the requested location. Accepts
+ transcripts exported by earlier releases. If parentID is supplied, the parent
+ session must already exist; import parents before children.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/api/experimental/session/import",
+ body=maybe_transform(
+ {
+ "info": info,
+ "messages": messages,
+ "location": location,
+ },
+ session_import__params.SessionImportParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionImportResponse,
+ )
+
+ def interrupt(
+ self,
+ session_id: SessionID,
+ *,
+ resume: Optional[bool] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionInterruptResponse:
+ """
+ Interrupt active execution owned by this OpenCode process. Returns
+ interrupted=true when an active execution was interrupted and false for the idle
+ no-op. When resume=true, execution resumes pending steering input and
+ next-in-line control items (manual compaction, moves) while queued prompts
+ remain parked.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._post(
+ path_template("/api/session/{session_id}/interrupt", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"resume": resume}, session_interrupt_params.SessionInterruptParams),
+ ),
+ cast_to=SessionInterruptResponse,
+ )
+
+ def log(
+ self,
+ session_id: SessionID,
+ *,
+ after: Optional[EventSeq] | Omit = omit,
+ follow: Optional[bool] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Experimental durable session event log. Reads events after an exclusive
+ aggregate sequence and continues with live events when follow=true.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._get(
+ path_template("/api/experimental/session/{session_id}/log", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "after": after,
+ "follow": follow,
+ },
+ session_log_params.SessionLogParams,
+ ),
+ ),
+ cast_to=NoneType,
+ )
+
+ def move(
+ self,
+ session_id: SessionID,
+ *,
+ directory: AbsolutePath,
+ delivery: Optional[SessionInboxDelivery] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Move a session to another project directory at the requested delivery boundary.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template("/api/session/{session_id}/move", session_id=session_id),
+ body=maybe_transform(
+ {
+ "directory": directory,
+ "delivery": delivery,
+ },
+ session_move_params.SessionMoveParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def prompt(
+ self,
+ session_id: SessionID,
+ *,
+ text: str,
+ id: Optional[SessionMessageID] | Omit = omit,
+ agents: Iterable[PromptAgentAttachment] | Omit = omit,
+ delivery: Optional[SessionInboxDelivery] | Omit = omit,
+ files: Iterable[PromptInputFileAttachment] | Omit = omit,
+ metadata: Dict[str, object] | Omit = omit,
+ resume: Optional[bool] | Omit = omit,
+ skills: Iterable[PromptInputSkillAttachment] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionPromptResponse:
+ """
+ Durably admit one session input and schedule agent-loop execution unless resume
+ is false.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._post(
+ path_template("/api/session/{session_id}/prompt", session_id=session_id),
+ body=maybe_transform(
+ {
+ "text": text,
+ "id": id,
+ "agents": agents,
+ "delivery": delivery,
+ "files": files,
+ "metadata": metadata,
+ "resume": resume,
+ "skills": skills,
+ },
+ session_prompt_params.SessionPromptParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionPromptResponse,
+ )
+
+ def remove(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Delete a session and its child sessions.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template("/api/session/{session_id}", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def shell(
+ self,
+ session_id: SessionID,
+ *,
+ command: str,
+ id: Optional[SessionMessageID] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Execute one shell command in the session's working directory. Emits a
+ shell.started event before execution and a shell.ended event with the merged
+ output after.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template("/api/session/{session_id}/shell", session_id=session_id),
+ body=maybe_transform(
+ {
+ "command": command,
+ "id": id,
+ },
+ session_shell_params.SessionShellParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def skill(
+ self,
+ session_id: SessionID,
+ *,
+ id: SkillID,
+ resume: Optional[bool] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Activate a skill for a session by appending a skill message and resuming
+ execution.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template("/api/experimental/session/{session_id}/skill", session_id=session_id),
+ body=maybe_transform(
+ {
+ "id": id,
+ "resume": resume,
+ },
+ session_skill_params.SessionSkillParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def stats(
+ self,
+ *,
+ from_: Union[Literal["Infinity", "-Infinity", "NaN"], float, None] | Omit = omit,
+ project: Optional[ProjectID] | Omit = omit,
+ timezone: Optional[str] | Omit = omit,
+ to: Union[Literal["Infinity", "-Infinity", "NaN"], float, None] | Omit = omit,
+ tools: Optional[Literal["none", "summary", "detail"]] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionStatsResponse:
+ """
+ Aggregate local session activity, usage, and tool reliability for a time range.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/experimental/session/stats",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "from_": from_,
+ "project": project,
+ "timezone": timezone,
+ "to": to,
+ "tools": tools,
+ },
+ session_stats_params.SessionStatsParams,
+ ),
+ ),
+ cast_to=SessionStatsResponse,
+ )
+
+ def switch_agent(
+ self,
+ session_id: SessionID,
+ *,
+ agent: AgentID,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Switch the agent used by subsequent provider turns.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template("/api/session/{session_id}/agent", session_id=session_id),
+ body=maybe_transform({"agent": agent}, session_switch_agent_params.SessionSwitchAgentParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def switch_model(
+ self,
+ session_id: SessionID,
+ *,
+ model: ModelRef,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Switch the model used by subsequent provider turns.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template("/api/session/{session_id}/model", session_id=session_id),
+ body=maybe_transform({"model": model}, session_switch_model_params.SessionSwitchModelParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def synthetic(
+ self,
+ session_id: SessionID,
+ *,
+ text: str,
+ id: Optional[SessionMessageID] | Omit = omit,
+ delivery: Optional[SessionInboxDelivery] | Omit = omit,
+ description: Optional[str] | Omit = omit,
+ metadata: Dict[str, object] | Omit = omit,
+ resume: Optional[bool] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionSyntheticResponse:
+ """
+ Durably admit synthetic session input and schedule execution unless resume is
+ false.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return self._post(
+ path_template("/api/session/{session_id}/synthetic", session_id=session_id),
+ body=maybe_transform(
+ {
+ "text": text,
+ "id": id,
+ "delivery": delivery,
+ "description": description,
+ "metadata": metadata,
+ "resume": resume,
+ },
+ session_synthetic_params.SessionSyntheticParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionSyntheticResponse,
+ )
+
+ def view(
+ self,
+ session_id: SessionID,
+ *,
+ idle: float,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Mark the idle transition observed by the viewer as viewed.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template("/api/session/{session_id}/view", session_id=session_id),
+ body=maybe_transform({"idle": idle}, session_view_params.SessionViewParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def wait(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Wait for a session agent loop to become idle.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template("/api/experimental/session/{session_id}/wait", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncSessionResource(AsyncAPIResource):
+ @cached_property
+ def revert(self) -> AsyncRevertResource:
+ return AsyncRevertResource(self._client)
+
+ @cached_property
+ def inbox(self) -> AsyncInboxResource:
+ return AsyncInboxResource(self._client)
+
+ @cached_property
+ def instructions(self) -> AsyncInstructionsResource:
+ return AsyncInstructionsResource(self._client)
+
+ @cached_property
+ def message(self) -> AsyncMessageResource:
+ return AsyncMessageResource(self._client)
+
+ @cached_property
+ def form(self) -> AsyncFormResource:
+ return AsyncFormResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncSessionResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncSessionResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncSessionResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncSessionResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ id: Optional[SessionID] | Omit = omit,
+ agent: Optional[AgentID] | Omit = omit,
+ location: Optional[LocationPublicRef] | Omit = omit,
+ metadata: Optional[SessionMetadata] | Omit = omit,
+ model: Optional[ModelRef] | Omit = omit,
+ parent_id: Optional[SessionID] | Omit = omit,
+ permissions: Optional[PermissionRuleset] | Omit = omit,
+ title: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionCreateResponse:
+ """
+ Create a session at the requested location. A parentID creates a linked child
+ session at its parent's location.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/api/session",
+ body=await async_maybe_transform(
+ {
+ "id": id,
+ "agent": agent,
+ "location": location,
+ "metadata": metadata,
+ "model": model,
+ "parent_id": parent_id,
+ "permissions": permissions,
+ "title": title,
+ },
+ session_create_params.SessionCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionCreateResponse,
+ )
+
+ async def update(
+ self,
+ session_id: SessionID,
+ *,
+ archived: Optional[bool] | Omit = omit,
+ metadata: Optional[SessionMetadata] | Omit = omit,
+ permissions: Optional[PermissionRuleset] | Omit = omit,
+ title: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Update mutable session properties.
+
+ Args:
+ archived: Archive the session with true or restore it with false. Repeating the current
+ state is a no-op.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._patch(
+ path_template("/api/session/{session_id}", session_id=session_id),
+ body=await async_maybe_transform(
+ {
+ "archived": archived,
+ "metadata": metadata,
+ "permissions": permissions,
+ "title": title,
+ },
+ session_update_params.SessionUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def list(
+ self,
+ *,
+ archived: Optional[bool] | Omit = omit,
+ cursor: Optional[SessionsCursor] | Omit = omit,
+ directory: Optional[AbsolutePath] | Omit = omit,
+ limit: Optional[int] | Omit = omit,
+ order: Optional[Literal["asc", "desc"]] | Omit = omit,
+ parent_id: Optional[SessionID] | Omit = omit,
+ project: Optional[ProjectID] | Omit = omit,
+ search: Optional[str] | Omit = omit,
+ subpath: Optional[RelativePath] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AsyncPaginator[SessionInfo, AsyncCursorPage[SessionInfo]]:
+ """
+ Retrieve sessions in the requested order. Items keep that order across pages;
+ use cursor.next or cursor.previous to move through the ordered list.
+
+ Args:
+ limit: Maximum number of sessions to return. Defaults to the newest 50 sessions.
+
+ order: Session order for the first page. Use desc for newest first or asc for oldest
+ first.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get_api_list(
+ "/api/session",
+ page=AsyncCursorPage[SessionInfo],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "archived": archived,
+ "cursor": cursor,
+ "directory": directory,
+ "limit": limit,
+ "order": order,
+ "parent_id": parent_id,
+ "project": project,
+ "search": search,
+ "subpath": subpath,
+ },
+ session_list_params.SessionListParams,
+ ),
+ ),
+ model=SessionInfo,
+ )
+
+ async def active(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionActiveResponse:
+ """
+ Retrieve foreground Session drains currently owned by this OpenCode process.
+ Sessions absent from the result are inactive.
+ """
+ return await self._get(
+ "/api/session/active",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionActiveResponse,
+ )
+
+ async def background(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Move active foreground backgroundable tools for this session into background
+ observation. Idle requests are a no-op.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template("/api/session/{session_id}/background", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def command(
+ self,
+ session_id: SessionID,
+ *,
+ name: str,
+ text: str,
+ agents: Iterable[PromptAgentAttachment] | Omit = omit,
+ delivery: Optional[SessionInboxDelivery] | Omit = omit,
+ files: Iterable[PromptInputFileAttachment] | Omit = omit,
+ skills: Iterable[PromptInputSkillAttachment] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Execute a slash command callback immediately.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template("/api/session/{session_id}/command", session_id=session_id),
+ body=await async_maybe_transform(
+ {
+ "name": name,
+ "text": text,
+ "agents": agents,
+ "delivery": delivery,
+ "files": files,
+ "skills": skills,
+ },
+ session_command_params.SessionCommandParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def compact(
+ self,
+ session_id: SessionID,
+ *,
+ id: Optional[SessionMessageID] | Omit = omit,
+ delivery: Optional[SessionInboxDelivery] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionCompactResponse:
+ """
+ Durably admit a session compaction request. Steers by default: it runs at the
+ next step boundary instead of waiting behind queued prompts.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._post(
+ path_template("/api/session/{session_id}/compact", session_id=session_id),
+ body=await async_maybe_transform(
+ {
+ "id": id,
+ "delivery": delivery,
+ },
+ session_compact_params.SessionCompactParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionCompactResponse,
+ )
+
+ async def context(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionContextResponse:
+ """
+ Retrieve the active context messages for a session (all messages after the last
+ compaction).
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._get(
+ path_template("/api/session/{session_id}/context", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionContextResponse,
+ )
+
+ async def diff(
+ self,
+ session_id: SessionID,
+ *,
+ context: Optional[int] | Omit = omit,
+ from_: Optional[SessionMessageID] | Omit = omit,
+ to: Optional[SessionMessageID] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionDiffResponse:
+ """
+ Structured per-file diffs of the files a turn changed. A turn runs from the
+ first prompt after the session was last idle until its next idle marker, so
+ prompts steered in while it was busy belong to the same turn; `to` extends the
+ range through a later turn. Compares the range's first recorded snapshot with
+ its last; a step still running in the active session compares against the
+ working copy. Ranges that span a location change are rejected. In sessions
+ without any idle marker, a prompt's turn spans until the next user message.
+
+ Args:
+ context: Unchanged lines around each hunk. Omit for full-file patches.
+
+ from_: User message whose turn to diff. Defaults to the turn of the newest user
+ message.
+
+ to: Later user message whose turn ends the range. Defaults to the turn of `from`
+ alone.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._get(
+ path_template("/api/session/{session_id}/diff", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "context": context,
+ "from_": from_,
+ "to": to,
+ },
+ session_diff_params.SessionDiffParams,
+ ),
+ ),
+ cast_to=SessionDiffResponse,
+ )
+
+ async def environment(
+ self,
+ session_id: SessionID,
+ *,
+ variables: Dict[str, str],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Replace the process environment used by local shell commands for this session.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._put(
+ path_template("/api/session/{session_id}/environment", session_id=session_id),
+ body=await async_maybe_transform(
+ {"variables": variables}, session_environment_params.SessionEnvironmentParams
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def export(
+ self,
+ session_id: SessionID,
+ *,
+ sanitize: Optional[bool] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionExportResponse:
+ """
+ Export a complete projected session transcript.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._get(
+ path_template("/api/experimental/session/{session_id}/export", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"sanitize": sanitize}, session_export_params.SessionExportParams),
+ ),
+ cast_to=SessionExportResponse,
+ )
+
+ async def fork(
+ self,
+ session_id: SessionID,
+ *,
+ before: Optional[SessionMessageID] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionForkResponse:
+ """
+ Create a child session by copying projected history before a message. Omit
+ before to copy the full history.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._post(
+ path_template("/api/session/{session_id}/fork", session_id=session_id),
+ body=await async_maybe_transform({"before": before}, session_fork_params.SessionForkParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionForkResponse,
+ )
+
+ async def generate(
+ self,
+ session_id: SessionID,
+ *,
+ prompt: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionGenerateResponse:
+ """
+ Generate transient text from the current session context without mutating
+ session history.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._post(
+ path_template("/api/session/{session_id}/generate", session_id=session_id),
+ body=await async_maybe_transform({"prompt": prompt}, session_generate_params.SessionGenerateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionGenerateResponse,
+ )
+
+ async def get(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionGetResponse:
+ """
+ Retrieve a session by ID.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._get(
+ path_template("/api/session/{session_id}", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionGetResponse,
+ )
+
+ async def import_(
+ self,
+ *,
+ info: SessionInfo,
+ messages: Iterable[object],
+ location: Optional[LocationPublicRef] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionImportResponse:
+ """
+ Import a projected session transcript at the requested location. Accepts
+ transcripts exported by earlier releases. If parentID is supplied, the parent
+ session must already exist; import parents before children.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/api/experimental/session/import",
+ body=await async_maybe_transform(
+ {
+ "info": info,
+ "messages": messages,
+ "location": location,
+ },
+ session_import__params.SessionImportParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionImportResponse,
+ )
+
+ async def interrupt(
+ self,
+ session_id: SessionID,
+ *,
+ resume: Optional[bool] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionInterruptResponse:
+ """
+ Interrupt active execution owned by this OpenCode process. Returns
+ interrupted=true when an active execution was interrupted and false for the idle
+ no-op. When resume=true, execution resumes pending steering input and
+ next-in-line control items (manual compaction, moves) while queued prompts
+ remain parked.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._post(
+ path_template("/api/session/{session_id}/interrupt", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"resume": resume}, session_interrupt_params.SessionInterruptParams),
+ ),
+ cast_to=SessionInterruptResponse,
+ )
+
+ async def log(
+ self,
+ session_id: SessionID,
+ *,
+ after: Optional[EventSeq] | Omit = omit,
+ follow: Optional[bool] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Experimental durable session event log. Reads events after an exclusive
+ aggregate sequence and continues with live events when follow=true.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._get(
+ path_template("/api/experimental/session/{session_id}/log", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "after": after,
+ "follow": follow,
+ },
+ session_log_params.SessionLogParams,
+ ),
+ ),
+ cast_to=NoneType,
+ )
+
+ async def move(
+ self,
+ session_id: SessionID,
+ *,
+ directory: AbsolutePath,
+ delivery: Optional[SessionInboxDelivery] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Move a session to another project directory at the requested delivery boundary.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template("/api/session/{session_id}/move", session_id=session_id),
+ body=await async_maybe_transform(
+ {
+ "directory": directory,
+ "delivery": delivery,
+ },
+ session_move_params.SessionMoveParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def prompt(
+ self,
+ session_id: SessionID,
+ *,
+ text: str,
+ id: Optional[SessionMessageID] | Omit = omit,
+ agents: Iterable[PromptAgentAttachment] | Omit = omit,
+ delivery: Optional[SessionInboxDelivery] | Omit = omit,
+ files: Iterable[PromptInputFileAttachment] | Omit = omit,
+ metadata: Dict[str, object] | Omit = omit,
+ resume: Optional[bool] | Omit = omit,
+ skills: Iterable[PromptInputSkillAttachment] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionPromptResponse:
+ """
+ Durably admit one session input and schedule agent-loop execution unless resume
+ is false.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._post(
+ path_template("/api/session/{session_id}/prompt", session_id=session_id),
+ body=await async_maybe_transform(
+ {
+ "text": text,
+ "id": id,
+ "agents": agents,
+ "delivery": delivery,
+ "files": files,
+ "metadata": metadata,
+ "resume": resume,
+ "skills": skills,
+ },
+ session_prompt_params.SessionPromptParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionPromptResponse,
+ )
+
+ async def remove(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Delete a session and its child sessions.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template("/api/session/{session_id}", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def shell(
+ self,
+ session_id: SessionID,
+ *,
+ command: str,
+ id: Optional[SessionMessageID] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Execute one shell command in the session's working directory. Emits a
+ shell.started event before execution and a shell.ended event with the merged
+ output after.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template("/api/session/{session_id}/shell", session_id=session_id),
+ body=await async_maybe_transform(
+ {
+ "command": command,
+ "id": id,
+ },
+ session_shell_params.SessionShellParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def skill(
+ self,
+ session_id: SessionID,
+ *,
+ id: SkillID,
+ resume: Optional[bool] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Activate a skill for a session by appending a skill message and resuming
+ execution.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template("/api/experimental/session/{session_id}/skill", session_id=session_id),
+ body=await async_maybe_transform(
+ {
+ "id": id,
+ "resume": resume,
+ },
+ session_skill_params.SessionSkillParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def stats(
+ self,
+ *,
+ from_: Union[Literal["Infinity", "-Infinity", "NaN"], float, None] | Omit = omit,
+ project: Optional[ProjectID] | Omit = omit,
+ timezone: Optional[str] | Omit = omit,
+ to: Union[Literal["Infinity", "-Infinity", "NaN"], float, None] | Omit = omit,
+ tools: Optional[Literal["none", "summary", "detail"]] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionStatsResponse:
+ """
+ Aggregate local session activity, usage, and tool reliability for a time range.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/experimental/session/stats",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "from_": from_,
+ "project": project,
+ "timezone": timezone,
+ "to": to,
+ "tools": tools,
+ },
+ session_stats_params.SessionStatsParams,
+ ),
+ ),
+ cast_to=SessionStatsResponse,
+ )
+
+ async def switch_agent(
+ self,
+ session_id: SessionID,
+ *,
+ agent: AgentID,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Switch the agent used by subsequent provider turns.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template("/api/session/{session_id}/agent", session_id=session_id),
+ body=await async_maybe_transform({"agent": agent}, session_switch_agent_params.SessionSwitchAgentParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def switch_model(
+ self,
+ session_id: SessionID,
+ *,
+ model: ModelRef,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Switch the model used by subsequent provider turns.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template("/api/session/{session_id}/model", session_id=session_id),
+ body=await async_maybe_transform({"model": model}, session_switch_model_params.SessionSwitchModelParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def synthetic(
+ self,
+ session_id: SessionID,
+ *,
+ text: str,
+ id: Optional[SessionMessageID] | Omit = omit,
+ delivery: Optional[SessionInboxDelivery] | Omit = omit,
+ description: Optional[str] | Omit = omit,
+ metadata: Dict[str, object] | Omit = omit,
+ resume: Optional[bool] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SessionSyntheticResponse:
+ """
+ Durably admit synthetic session input and schedule execution unless resume is
+ false.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ return await self._post(
+ path_template("/api/session/{session_id}/synthetic", session_id=session_id),
+ body=await async_maybe_transform(
+ {
+ "text": text,
+ "id": id,
+ "delivery": delivery,
+ "description": description,
+ "metadata": metadata,
+ "resume": resume,
+ },
+ session_synthetic_params.SessionSyntheticParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SessionSyntheticResponse,
+ )
+
+ async def view(
+ self,
+ session_id: SessionID,
+ *,
+ idle: float,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Mark the idle transition observed by the viewer as viewed.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template("/api/session/{session_id}/view", session_id=session_id),
+ body=await async_maybe_transform({"idle": idle}, session_view_params.SessionViewParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def wait(
+ self,
+ session_id: SessionID,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Wait for a session agent loop to become idle.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not session_id:
+ raise ValueError(f"Expected a non-empty value for `session_id` but received {session_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template("/api/experimental/session/{session_id}/wait", session_id=session_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class SessionResourceWithRawResponse:
+ def __init__(self, session: SessionResource) -> None:
+ self._session = session
+
+ self.create = to_raw_response_wrapper(
+ session.create,
+ )
+ self.update = to_raw_response_wrapper(
+ session.update,
+ )
+ self.list = to_raw_response_wrapper(
+ session.list,
+ )
+ self.active = to_raw_response_wrapper(
+ session.active,
+ )
+ self.background = to_raw_response_wrapper(
+ session.background,
+ )
+ self.command = to_raw_response_wrapper(
+ session.command,
+ )
+ self.compact = to_raw_response_wrapper(
+ session.compact,
+ )
+ self.context = to_raw_response_wrapper(
+ session.context,
+ )
+ self.diff = to_raw_response_wrapper(
+ session.diff,
+ )
+ self.environment = to_raw_response_wrapper(
+ session.environment,
+ )
+ self.export = to_raw_response_wrapper(
+ session.export,
+ )
+ self.fork = to_raw_response_wrapper(
+ session.fork,
+ )
+ self.generate = to_raw_response_wrapper(
+ session.generate,
+ )
+ self.get = to_raw_response_wrapper(
+ session.get,
+ )
+ self.import_ = to_raw_response_wrapper(
+ session.import_,
+ )
+ self.interrupt = to_raw_response_wrapper(
+ session.interrupt,
+ )
+ self.log = to_raw_response_wrapper(
+ session.log,
+ )
+ self.move = to_raw_response_wrapper(
+ session.move,
+ )
+ self.prompt = to_raw_response_wrapper(
+ session.prompt,
+ )
+ self.remove = to_raw_response_wrapper(
+ session.remove,
+ )
+ self.shell = to_raw_response_wrapper(
+ session.shell,
+ )
+ self.skill = to_raw_response_wrapper(
+ session.skill,
+ )
+ self.stats = to_raw_response_wrapper(
+ session.stats,
+ )
+ self.switch_agent = to_raw_response_wrapper(
+ session.switch_agent,
+ )
+ self.switch_model = to_raw_response_wrapper(
+ session.switch_model,
+ )
+ self.synthetic = to_raw_response_wrapper(
+ session.synthetic,
+ )
+ self.view = to_raw_response_wrapper(
+ session.view,
+ )
+ self.wait = to_raw_response_wrapper(
+ session.wait,
+ )
+
+ @cached_property
+ def revert(self) -> RevertResourceWithRawResponse:
+ return RevertResourceWithRawResponse(self._session.revert)
+
+ @cached_property
+ def inbox(self) -> InboxResourceWithRawResponse:
+ return InboxResourceWithRawResponse(self._session.inbox)
+
+ @cached_property
+ def instructions(self) -> InstructionsResourceWithRawResponse:
+ return InstructionsResourceWithRawResponse(self._session.instructions)
+
+ @cached_property
+ def message(self) -> MessageResourceWithRawResponse:
+ return MessageResourceWithRawResponse(self._session.message)
+
+ @cached_property
+ def form(self) -> FormResourceWithRawResponse:
+ return FormResourceWithRawResponse(self._session.form)
+
+
+class AsyncSessionResourceWithRawResponse:
+ def __init__(self, session: AsyncSessionResource) -> None:
+ self._session = session
+
+ self.create = async_to_raw_response_wrapper(
+ session.create,
+ )
+ self.update = async_to_raw_response_wrapper(
+ session.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ session.list,
+ )
+ self.active = async_to_raw_response_wrapper(
+ session.active,
+ )
+ self.background = async_to_raw_response_wrapper(
+ session.background,
+ )
+ self.command = async_to_raw_response_wrapper(
+ session.command,
+ )
+ self.compact = async_to_raw_response_wrapper(
+ session.compact,
+ )
+ self.context = async_to_raw_response_wrapper(
+ session.context,
+ )
+ self.diff = async_to_raw_response_wrapper(
+ session.diff,
+ )
+ self.environment = async_to_raw_response_wrapper(
+ session.environment,
+ )
+ self.export = async_to_raw_response_wrapper(
+ session.export,
+ )
+ self.fork = async_to_raw_response_wrapper(
+ session.fork,
+ )
+ self.generate = async_to_raw_response_wrapper(
+ session.generate,
+ )
+ self.get = async_to_raw_response_wrapper(
+ session.get,
+ )
+ self.import_ = async_to_raw_response_wrapper(
+ session.import_,
+ )
+ self.interrupt = async_to_raw_response_wrapper(
+ session.interrupt,
+ )
+ self.log = async_to_raw_response_wrapper(
+ session.log,
+ )
+ self.move = async_to_raw_response_wrapper(
+ session.move,
+ )
+ self.prompt = async_to_raw_response_wrapper(
+ session.prompt,
+ )
+ self.remove = async_to_raw_response_wrapper(
+ session.remove,
+ )
+ self.shell = async_to_raw_response_wrapper(
+ session.shell,
+ )
+ self.skill = async_to_raw_response_wrapper(
+ session.skill,
+ )
+ self.stats = async_to_raw_response_wrapper(
+ session.stats,
+ )
+ self.switch_agent = async_to_raw_response_wrapper(
+ session.switch_agent,
+ )
+ self.switch_model = async_to_raw_response_wrapper(
+ session.switch_model,
+ )
+ self.synthetic = async_to_raw_response_wrapper(
+ session.synthetic,
+ )
+ self.view = async_to_raw_response_wrapper(
+ session.view,
+ )
+ self.wait = async_to_raw_response_wrapper(
+ session.wait,
+ )
+
+ @cached_property
+ def revert(self) -> AsyncRevertResourceWithRawResponse:
+ return AsyncRevertResourceWithRawResponse(self._session.revert)
+
+ @cached_property
+ def inbox(self) -> AsyncInboxResourceWithRawResponse:
+ return AsyncInboxResourceWithRawResponse(self._session.inbox)
+
+ @cached_property
+ def instructions(self) -> AsyncInstructionsResourceWithRawResponse:
+ return AsyncInstructionsResourceWithRawResponse(self._session.instructions)
+
+ @cached_property
+ def message(self) -> AsyncMessageResourceWithRawResponse:
+ return AsyncMessageResourceWithRawResponse(self._session.message)
+
+ @cached_property
+ def form(self) -> AsyncFormResourceWithRawResponse:
+ return AsyncFormResourceWithRawResponse(self._session.form)
+
+
+class SessionResourceWithStreamingResponse:
+ def __init__(self, session: SessionResource) -> None:
+ self._session = session
+
+ self.create = to_streamed_response_wrapper(
+ session.create,
+ )
+ self.update = to_streamed_response_wrapper(
+ session.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ session.list,
+ )
+ self.active = to_streamed_response_wrapper(
+ session.active,
+ )
+ self.background = to_streamed_response_wrapper(
+ session.background,
+ )
+ self.command = to_streamed_response_wrapper(
+ session.command,
+ )
+ self.compact = to_streamed_response_wrapper(
+ session.compact,
+ )
+ self.context = to_streamed_response_wrapper(
+ session.context,
+ )
+ self.diff = to_streamed_response_wrapper(
+ session.diff,
+ )
+ self.environment = to_streamed_response_wrapper(
+ session.environment,
+ )
+ self.export = to_streamed_response_wrapper(
+ session.export,
+ )
+ self.fork = to_streamed_response_wrapper(
+ session.fork,
+ )
+ self.generate = to_streamed_response_wrapper(
+ session.generate,
+ )
+ self.get = to_streamed_response_wrapper(
+ session.get,
+ )
+ self.import_ = to_streamed_response_wrapper(
+ session.import_,
+ )
+ self.interrupt = to_streamed_response_wrapper(
+ session.interrupt,
+ )
+ self.log = to_streamed_response_wrapper(
+ session.log,
+ )
+ self.move = to_streamed_response_wrapper(
+ session.move,
+ )
+ self.prompt = to_streamed_response_wrapper(
+ session.prompt,
+ )
+ self.remove = to_streamed_response_wrapper(
+ session.remove,
+ )
+ self.shell = to_streamed_response_wrapper(
+ session.shell,
+ )
+ self.skill = to_streamed_response_wrapper(
+ session.skill,
+ )
+ self.stats = to_streamed_response_wrapper(
+ session.stats,
+ )
+ self.switch_agent = to_streamed_response_wrapper(
+ session.switch_agent,
+ )
+ self.switch_model = to_streamed_response_wrapper(
+ session.switch_model,
+ )
+ self.synthetic = to_streamed_response_wrapper(
+ session.synthetic,
+ )
+ self.view = to_streamed_response_wrapper(
+ session.view,
+ )
+ self.wait = to_streamed_response_wrapper(
+ session.wait,
+ )
+
+ @cached_property
+ def revert(self) -> RevertResourceWithStreamingResponse:
+ return RevertResourceWithStreamingResponse(self._session.revert)
+
+ @cached_property
+ def inbox(self) -> InboxResourceWithStreamingResponse:
+ return InboxResourceWithStreamingResponse(self._session.inbox)
+
+ @cached_property
+ def instructions(self) -> InstructionsResourceWithStreamingResponse:
+ return InstructionsResourceWithStreamingResponse(self._session.instructions)
+
+ @cached_property
+ def message(self) -> MessageResourceWithStreamingResponse:
+ return MessageResourceWithStreamingResponse(self._session.message)
+
+ @cached_property
+ def form(self) -> FormResourceWithStreamingResponse:
+ return FormResourceWithStreamingResponse(self._session.form)
+
+
+class AsyncSessionResourceWithStreamingResponse:
+ def __init__(self, session: AsyncSessionResource) -> None:
+ self._session = session
+
+ self.create = async_to_streamed_response_wrapper(
+ session.create,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ session.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ session.list,
+ )
+ self.active = async_to_streamed_response_wrapper(
+ session.active,
+ )
+ self.background = async_to_streamed_response_wrapper(
+ session.background,
+ )
+ self.command = async_to_streamed_response_wrapper(
+ session.command,
+ )
+ self.compact = async_to_streamed_response_wrapper(
+ session.compact,
+ )
+ self.context = async_to_streamed_response_wrapper(
+ session.context,
+ )
+ self.diff = async_to_streamed_response_wrapper(
+ session.diff,
+ )
+ self.environment = async_to_streamed_response_wrapper(
+ session.environment,
+ )
+ self.export = async_to_streamed_response_wrapper(
+ session.export,
+ )
+ self.fork = async_to_streamed_response_wrapper(
+ session.fork,
+ )
+ self.generate = async_to_streamed_response_wrapper(
+ session.generate,
+ )
+ self.get = async_to_streamed_response_wrapper(
+ session.get,
+ )
+ self.import_ = async_to_streamed_response_wrapper(
+ session.import_,
+ )
+ self.interrupt = async_to_streamed_response_wrapper(
+ session.interrupt,
+ )
+ self.log = async_to_streamed_response_wrapper(
+ session.log,
+ )
+ self.move = async_to_streamed_response_wrapper(
+ session.move,
+ )
+ self.prompt = async_to_streamed_response_wrapper(
+ session.prompt,
+ )
+ self.remove = async_to_streamed_response_wrapper(
+ session.remove,
+ )
+ self.shell = async_to_streamed_response_wrapper(
+ session.shell,
+ )
+ self.skill = async_to_streamed_response_wrapper(
+ session.skill,
+ )
+ self.stats = async_to_streamed_response_wrapper(
+ session.stats,
+ )
+ self.switch_agent = async_to_streamed_response_wrapper(
+ session.switch_agent,
+ )
+ self.switch_model = async_to_streamed_response_wrapper(
+ session.switch_model,
+ )
+ self.synthetic = async_to_streamed_response_wrapper(
+ session.synthetic,
+ )
+ self.view = async_to_streamed_response_wrapper(
+ session.view,
+ )
+ self.wait = async_to_streamed_response_wrapper(
+ session.wait,
+ )
+
+ @cached_property
+ def revert(self) -> AsyncRevertResourceWithStreamingResponse:
+ return AsyncRevertResourceWithStreamingResponse(self._session.revert)
+
+ @cached_property
+ def inbox(self) -> AsyncInboxResourceWithStreamingResponse:
+ return AsyncInboxResourceWithStreamingResponse(self._session.inbox)
+
+ @cached_property
+ def instructions(self) -> AsyncInstructionsResourceWithStreamingResponse:
+ return AsyncInstructionsResourceWithStreamingResponse(self._session.instructions)
+
+ @cached_property
+ def message(self) -> AsyncMessageResourceWithStreamingResponse:
+ return AsyncMessageResourceWithStreamingResponse(self._session.message)
+
+ @cached_property
+ def form(self) -> AsyncFormResourceWithStreamingResponse:
+ return AsyncFormResourceWithStreamingResponse(self._session.form)
diff --git a/src/opencode/resources/shell.py b/src/opencode/resources/shell.py
index b7db519..ea1b71d 100644
--- a/src/opencode/resources/shell.py
+++ b/src/opencode/resources/shell.py
@@ -1,493 +1,571 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``shell`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from collections.abc import Mapping
-from functools import cached_property
-from typing import TYPE_CHECKING, Any, cast
+from typing import Dict, Optional
+
+import httpx
-from .._base_client import RequestOptions
-from .._qs import encode_path_segment
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import shell_get_params, shell_list_params, shell_create_params, shell_output_params, shell_remove_params
+from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from .._utils import path_template, maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- Location,
- ShellCreateResponse,
- ShellGetResponse,
- ShellID,
- ShellListResponse,
- ShellOutputResponse,
- )
+from .._base_client import make_request_options
+from ..types.shared.shell_id import ShellID
+from ..types.shell_get_response import ShellGetResponse
+from ..types.shell_list_response import ShellListResponse
+from ..types.shell_create_response import ShellCreateResponse
+from ..types.shell_output_response import ShellOutputResponse
__all__ = ["ShellResource", "AsyncShellResource"]
class ShellResource(SyncAPIResource):
- """Operations under ``client.shell`` (OpenCode ``shell``)."""
-
@cached_property
def with_raw_response(self) -> ShellResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return ShellResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> ShellResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return ShellResourceWithStreamingResponse(self)
- def list(
+ def create(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ command: str,
+ cwd: str | Omit = omit,
+ location: Optional[shell_create_params.Location] | Omit = omit,
+ metadata: Dict[str, object] | Omit = omit,
+ api_timeout: int | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> ShellListResponse:
- """List running shell commands.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ShellCreateResponse:
+ """
+ Spawn one non-interactive shell command for a location. Combined stdout/stderr
+ is captured to a file pageable via output.
- List currently running shell commands for a location. Exited commands are not included.
+ Args:
+ extra_headers: Send extra headers
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ extra_query: Add additional query parameters to the request
- ``GET /api/shell``
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "ShellListResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/shell",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ShellListResponse",
+ return self._post(
+ "/api/shell",
+ body=maybe_transform(
+ {
+ "command": command,
+ "cwd": cwd,
+ "metadata": metadata,
+ "api_timeout": api_timeout,
+ },
+ shell_create_params.ShellCreateParams,
),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, shell_create_params.ShellCreateParams),
+ ),
+ cast_to=ShellCreateResponse,
)
- def create(
+ def list(
self,
*,
- command: str,
- location: Location | NotGiven | None = NOT_GIVEN,
- cwd: str | NotGiven = NOT_GIVEN,
- timeout_: int | NotGiven = NOT_GIVEN,
- metadata: Mapping[str, Any] | NotGiven = NOT_GIVEN,
+ location: Optional[shell_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> ShellCreateResponse:
- """Run shell command.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ShellListResponse:
+ """
+ List currently running shell commands for a location. Exited commands are not
+ included.
+
+ Args:
+ extra_headers: Send extra headers
- Spawn one non-interactive shell command for a location. Combined stdout/stderr is captured to a file pageable via output.
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ extra_body: Add additional JSON properties to the request
- ``POST /api/shell``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "ShellCreateResponse",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/shell",
- query={"location": self._location(location)},
- body={"command": command, "cwd": cwd, "timeout": timeout_, "metadata": metadata},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ShellCreateResponse",
+ return self._get(
+ "/api/shell",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, shell_list_params.ShellListParams),
),
+ cast_to=ShellListResponse,
)
def get(
self,
id: ShellID,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[shell_get_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ShellGetResponse:
- """Get shell command.
-
+ """
Get one shell command, including its status and exit code once exited.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ShellNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
- ``GET /api/shell/:id``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "ShellGetResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/shell/{encode_path_segment(id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ShellGetResponse",
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._get(
+ path_template("/api/shell/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, shell_get_params.ShellGetParams),
),
+ cast_to=ShellGetResponse,
)
def output(
self,
id: ShellID,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
- cursor: int | NotGiven | None = NOT_GIVEN,
- limit: int | NotGiven | None = NOT_GIVEN,
+ cursor: Optional[int] | Omit = omit,
+ limit: Optional[int] | Omit = omit,
+ location: Optional[shell_output_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ShellOutputResponse:
- """Read shell output.
-
+ """
Page through captured combined output by absolute byte cursor.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ShellNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
- ``GET /api/shell/:id/output``
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "ShellOutputResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/shell/{encode_path_segment(id)}/output",
- query={"location": self._location(location), "cursor": cursor, "limit": limit},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return self._get(
+ path_template("/api/shell/{id}/output", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "cursor": cursor,
+ "limit": limit,
+ "location": location,
+ },
+ shell_output_params.ShellOutputParams,
),
- response_type="ShellOutputResponse",
),
+ cast_to=ShellOutputResponse,
)
def remove(
self,
id: ShellID,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[shell_remove_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Remove shell command.
-
+ """
Terminate and remove one shell command and its retained output.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
- ``DELETE /api/shell/:id``
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/shell/{encode_path_segment(id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template("/api/shell/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, shell_remove_params.ShellRemoveParams),
),
+ cast_to=NoneType,
)
-class ShellResourceWithRawResponse:
- """``client.shell.with_raw_response``."""
-
- def __init__(self, resource: ShellResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
- self.create = to_raw_response_wrapper(resource.create)
- self.get = to_raw_response_wrapper(resource.get)
- self.output = to_raw_response_wrapper(resource.output)
- self.remove = to_raw_response_wrapper(resource.remove)
-
-
-class ShellResourceWithStreamingResponse:
- """``client.shell.with_streaming_response``."""
-
- def __init__(self, resource: ShellResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
- self.create = to_streamed_response_wrapper(resource.create)
- self.get = to_streamed_response_wrapper(resource.get)
- self.output = to_streamed_response_wrapper(resource.output)
- self.remove = to_streamed_response_wrapper(resource.remove)
-
-
class AsyncShellResource(AsyncAPIResource):
- """Operations under ``client.shell`` (OpenCode ``shell``)."""
-
@cached_property
def with_raw_response(self) -> AsyncShellResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncShellResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncShellResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncShellResourceWithStreamingResponse(self)
- async def list(
+ async def create(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ command: str,
+ cwd: str | Omit = omit,
+ location: Optional[shell_create_params.Location] | Omit = omit,
+ metadata: Dict[str, object] | Omit = omit,
+ api_timeout: int | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> ShellListResponse:
- """List running shell commands.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ShellCreateResponse:
+ """
+ Spawn one non-interactive shell command for a location. Combined stdout/stderr
+ is captured to a file pageable via output.
+
+ Args:
+ extra_headers: Send extra headers
- List currently running shell commands for a location. Exited commands are not included.
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ extra_body: Add additional JSON properties to the request
- ``GET /api/shell``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "ShellListResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/shell",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ShellListResponse",
+ return await self._post(
+ "/api/shell",
+ body=await async_maybe_transform(
+ {
+ "command": command,
+ "cwd": cwd,
+ "metadata": metadata,
+ "api_timeout": api_timeout,
+ },
+ shell_create_params.ShellCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, shell_create_params.ShellCreateParams),
),
+ cast_to=ShellCreateResponse,
)
- async def create(
+ async def list(
self,
*,
- command: str,
- location: Location | NotGiven | None = NOT_GIVEN,
- cwd: str | NotGiven = NOT_GIVEN,
- timeout_: int | NotGiven = NOT_GIVEN,
- metadata: Mapping[str, Any] | NotGiven = NOT_GIVEN,
+ location: Optional[shell_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> ShellCreateResponse:
- """Run shell command.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ShellListResponse:
+ """
+ List currently running shell commands for a location. Exited commands are not
+ included.
+
+ Args:
+ extra_headers: Send extra headers
- Spawn one non-interactive shell command for a location. Combined stdout/stderr is captured to a file pageable via output.
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ extra_body: Add additional JSON properties to the request
- ``POST /api/shell``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "ShellCreateResponse",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/shell",
- query={"location": self._location(location)},
- body={"command": command, "cwd": cwd, "timeout": timeout_, "metadata": metadata},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ShellCreateResponse",
+ return await self._get(
+ "/api/shell",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, shell_list_params.ShellListParams),
),
+ cast_to=ShellListResponse,
)
async def get(
self,
id: ShellID,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[shell_get_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ShellGetResponse:
- """Get shell command.
-
+ """
Get one shell command, including its status and exit code once exited.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ShellNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
- ``GET /api/shell/:id``
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "ShellGetResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/shell/{encode_path_segment(id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ShellGetResponse",
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._get(
+ path_template("/api/shell/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, shell_get_params.ShellGetParams),
),
+ cast_to=ShellGetResponse,
)
async def output(
self,
id: ShellID,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
- cursor: int | NotGiven | None = NOT_GIVEN,
- limit: int | NotGiven | None = NOT_GIVEN,
+ cursor: Optional[int] | Omit = omit,
+ limit: Optional[int] | Omit = omit,
+ location: Optional[shell_output_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ShellOutputResponse:
- """Read shell output.
-
+ """
Page through captured combined output by absolute byte cursor.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ShellNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
+ Args:
+ extra_headers: Send extra headers
- ``GET /api/shell/:id/output``
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "ShellOutputResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/shell/{encode_path_segment(id)}/output",
- query={"location": self._location(location), "cursor": cursor, "limit": limit},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ return await self._get(
+ path_template("/api/shell/{id}/output", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "cursor": cursor,
+ "limit": limit,
+ "location": location,
+ },
+ shell_output_params.ShellOutputParams,
),
- response_type="ShellOutputResponse",
),
+ cast_to=ShellOutputResponse,
)
async def remove(
self,
id: ShellID,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[shell_remove_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Remove shell command.
-
+ """
Terminate and remove one shell command and its retained output.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ Args:
+ extra_headers: Send extra headers
- ``DELETE /api/shell/:id``
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/shell/{encode_path_segment(id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
+ if not id:
+ raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template("/api/shell/{id}", id=id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, shell_remove_params.ShellRemoveParams),
),
+ cast_to=NoneType,
+ )
+
+
+class ShellResourceWithRawResponse:
+ def __init__(self, shell: ShellResource) -> None:
+ self._shell = shell
+
+ self.create = to_raw_response_wrapper(
+ shell.create,
+ )
+ self.list = to_raw_response_wrapper(
+ shell.list,
+ )
+ self.get = to_raw_response_wrapper(
+ shell.get,
+ )
+ self.output = to_raw_response_wrapper(
+ shell.output,
+ )
+ self.remove = to_raw_response_wrapper(
+ shell.remove,
)
class AsyncShellResourceWithRawResponse:
- """``client.shell.with_raw_response``."""
+ def __init__(self, shell: AsyncShellResource) -> None:
+ self._shell = shell
- def __init__(self, resource: AsyncShellResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
- self.create = async_to_raw_response_wrapper(resource.create)
- self.get = async_to_raw_response_wrapper(resource.get)
- self.output = async_to_raw_response_wrapper(resource.output)
- self.remove = async_to_raw_response_wrapper(resource.remove)
+ self.create = async_to_raw_response_wrapper(
+ shell.create,
+ )
+ self.list = async_to_raw_response_wrapper(
+ shell.list,
+ )
+ self.get = async_to_raw_response_wrapper(
+ shell.get,
+ )
+ self.output = async_to_raw_response_wrapper(
+ shell.output,
+ )
+ self.remove = async_to_raw_response_wrapper(
+ shell.remove,
+ )
+
+
+class ShellResourceWithStreamingResponse:
+ def __init__(self, shell: ShellResource) -> None:
+ self._shell = shell
+
+ self.create = to_streamed_response_wrapper(
+ shell.create,
+ )
+ self.list = to_streamed_response_wrapper(
+ shell.list,
+ )
+ self.get = to_streamed_response_wrapper(
+ shell.get,
+ )
+ self.output = to_streamed_response_wrapper(
+ shell.output,
+ )
+ self.remove = to_streamed_response_wrapper(
+ shell.remove,
+ )
class AsyncShellResourceWithStreamingResponse:
- """``client.shell.with_streaming_response``."""
-
- def __init__(self, resource: AsyncShellResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.create = async_to_streamed_response_wrapper(resource.create)
- self.get = async_to_streamed_response_wrapper(resource.get)
- self.output = async_to_streamed_response_wrapper(resource.output)
- self.remove = async_to_streamed_response_wrapper(resource.remove)
+ def __init__(self, shell: AsyncShellResource) -> None:
+ self._shell = shell
+
+ self.create = async_to_streamed_response_wrapper(
+ shell.create,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ shell.list,
+ )
+ self.get = async_to_streamed_response_wrapper(
+ shell.get,
+ )
+ self.output = async_to_streamed_response_wrapper(
+ shell.output,
+ )
+ self.remove = async_to_streamed_response_wrapper(
+ shell.remove,
+ )
diff --git a/src/opencode/resources/skill.py b/src/opencode/resources/skill.py
index e8f85d9..92b0c5c 100644
--- a/src/opencode/resources/skill.py
+++ b/src/opencode/resources/skill.py
@@ -1,159 +1,171 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``skill`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
+from typing import Optional
+
+import httpx
-from .._base_client import RequestOptions
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import skill_list_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- Location,
- SkillListResponse,
- )
+from .._base_client import make_request_options
+from ..types.skill_list_response import SkillListResponse
__all__ = ["SkillResource", "AsyncSkillResource"]
class SkillResource(SyncAPIResource):
- """Operations under ``client.skill`` (OpenCode ``skill``)."""
-
@cached_property
def with_raw_response(self) -> SkillResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return SkillResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> SkillResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return SkillResourceWithStreamingResponse(self)
def list(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[skill_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> SkillListResponse:
- """List skills.
-
+ """
Retrieve currently registered skills.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ Args:
+ extra_headers: Send extra headers
- ``GET /api/skill``
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "SkillListResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/skill",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SkillListResponse",
+ return self._get(
+ "/api/skill",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, skill_list_params.SkillListParams),
),
+ cast_to=SkillListResponse,
)
-class SkillResourceWithRawResponse:
- """``client.skill.with_raw_response``."""
-
- def __init__(self, resource: SkillResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
-
-
-class SkillResourceWithStreamingResponse:
- """``client.skill.with_streaming_response``."""
-
- def __init__(self, resource: SkillResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
-
-
class AsyncSkillResource(AsyncAPIResource):
- """Operations under ``client.skill`` (OpenCode ``skill``)."""
-
@cached_property
def with_raw_response(self) -> AsyncSkillResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncSkillResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncSkillResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncSkillResourceWithStreamingResponse(self)
async def list(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[skill_list_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> SkillListResponse:
- """List skills.
-
+ """
Retrieve currently registered skills.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
- ``GET /api/skill``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "SkillListResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/skill",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SkillListResponse",
+ return await self._get(
+ "/api/skill",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, skill_list_params.SkillListParams),
),
+ cast_to=SkillListResponse,
+ )
+
+
+class SkillResourceWithRawResponse:
+ def __init__(self, skill: SkillResource) -> None:
+ self._skill = skill
+
+ self.list = to_raw_response_wrapper(
+ skill.list,
)
class AsyncSkillResourceWithRawResponse:
- """``client.skill.with_raw_response``."""
+ def __init__(self, skill: AsyncSkillResource) -> None:
+ self._skill = skill
+
+ self.list = async_to_raw_response_wrapper(
+ skill.list,
+ )
+
+
+class SkillResourceWithStreamingResponse:
+ def __init__(self, skill: SkillResource) -> None:
+ self._skill = skill
- def __init__(self, resource: AsyncSkillResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
+ self.list = to_streamed_response_wrapper(
+ skill.list,
+ )
class AsyncSkillResourceWithStreamingResponse:
- """``client.skill.with_streaming_response``."""
+ def __init__(self, skill: AsyncSkillResource) -> None:
+ self._skill = skill
- def __init__(self, resource: AsyncSkillResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
+ self.list = async_to_streamed_response_wrapper(
+ skill.list,
+ )
diff --git a/src/opencode/resources/vcs/__init__.py b/src/opencode/resources/vcs/__init__.py
new file mode 100644
index 0000000..7187086
--- /dev/null
+++ b/src/opencode/resources/vcs/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from .vcs import (
+ VcsResource,
+ AsyncVcsResource,
+ VcsResourceWithRawResponse,
+ AsyncVcsResourceWithRawResponse,
+ VcsResourceWithStreamingResponse,
+ AsyncVcsResourceWithStreamingResponse,
+)
+from .branch import (
+ BranchResource,
+ AsyncBranchResource,
+ BranchResourceWithRawResponse,
+ AsyncBranchResourceWithRawResponse,
+ BranchResourceWithStreamingResponse,
+ AsyncBranchResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "BranchResource",
+ "AsyncBranchResource",
+ "BranchResourceWithRawResponse",
+ "AsyncBranchResourceWithRawResponse",
+ "BranchResourceWithStreamingResponse",
+ "AsyncBranchResourceWithStreamingResponse",
+ "VcsResource",
+ "AsyncVcsResource",
+ "VcsResourceWithRawResponse",
+ "AsyncVcsResourceWithRawResponse",
+ "VcsResourceWithStreamingResponse",
+ "AsyncVcsResourceWithStreamingResponse",
+]
diff --git a/src/opencode/resources/vcs/branch.py b/src/opencode/resources/vcs/branch.py
new file mode 100644
index 0000000..477d1e8
--- /dev/null
+++ b/src/opencode/resources/vcs/branch.py
@@ -0,0 +1,189 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...types.vcs import branch_list_params
+from ..._base_client import make_request_options
+from ...types.vcs.branch_list_response import BranchListResponse
+
+__all__ = ["BranchResource", "AsyncBranchResource"]
+
+
+class BranchResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> BranchResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return BranchResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> BranchResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return BranchResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ *,
+ limit: Optional[int] | Omit = omit,
+ location: Optional[branch_list_params.Location] | Omit = omit,
+ search: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BranchListResponse:
+ """
+ List local and remote branches available at the requested location.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/vcs/branch",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "location": location,
+ "search": search,
+ },
+ branch_list_params.BranchListParams,
+ ),
+ ),
+ cast_to=BranchListResponse,
+ )
+
+
+class AsyncBranchResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncBranchResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncBranchResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncBranchResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncBranchResourceWithStreamingResponse(self)
+
+ async def list(
+ self,
+ *,
+ limit: Optional[int] | Omit = omit,
+ location: Optional[branch_list_params.Location] | Omit = omit,
+ search: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BranchListResponse:
+ """
+ List local and remote branches available at the requested location.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/vcs/branch",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "limit": limit,
+ "location": location,
+ "search": search,
+ },
+ branch_list_params.BranchListParams,
+ ),
+ ),
+ cast_to=BranchListResponse,
+ )
+
+
+class BranchResourceWithRawResponse:
+ def __init__(self, branch: BranchResource) -> None:
+ self._branch = branch
+
+ self.list = to_raw_response_wrapper(
+ branch.list,
+ )
+
+
+class AsyncBranchResourceWithRawResponse:
+ def __init__(self, branch: AsyncBranchResource) -> None:
+ self._branch = branch
+
+ self.list = async_to_raw_response_wrapper(
+ branch.list,
+ )
+
+
+class BranchResourceWithStreamingResponse:
+ def __init__(self, branch: BranchResource) -> None:
+ self._branch = branch
+
+ self.list = to_streamed_response_wrapper(
+ branch.list,
+ )
+
+
+class AsyncBranchResourceWithStreamingResponse:
+ def __init__(self, branch: AsyncBranchResource) -> None:
+ self._branch = branch
+
+ self.list = async_to_streamed_response_wrapper(
+ branch.list,
+ )
diff --git a/src/opencode/resources/vcs/vcs.py b/src/opencode/resources/vcs/vcs.py
new file mode 100644
index 0000000..3b91fea
--- /dev/null
+++ b/src/opencode/resources/vcs/vcs.py
@@ -0,0 +1,591 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+
+import httpx
+
+from .branch import (
+ BranchResource,
+ AsyncBranchResource,
+ BranchResourceWithRawResponse,
+ AsyncBranchResourceWithRawResponse,
+ BranchResourceWithStreamingResponse,
+ AsyncBranchResourceWithStreamingResponse,
+)
+from ...types import vc_get_params, vc_base_params, vc_diff_params, vc_init_params, vc_status_params
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.shared.vcs_mode import VcsMode
+from ...types.vc_get_response import VcGetResponse
+from ...types.vc_base_response import VcBaseResponse
+from ...types.vc_diff_response import VcDiffResponse
+from ...types.vc_status_response import VcStatusResponse
+
+__all__ = ["VcsResource", "AsyncVcsResource"]
+
+
+class VcsResource(SyncAPIResource):
+ @cached_property
+ def branch(self) -> BranchResource:
+ return BranchResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> VcsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return VcsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> VcsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return VcsResourceWithStreamingResponse(self)
+
+ def base(
+ self,
+ *,
+ location: Optional[vc_base_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VcBaseResponse:
+ """
+ Infer a local review base from named branch creation history, or the repository
+ default only when currently on that branch. Returns null before the first commit
+ or when the provider lacks base metadata; ambiguous Git history requires an
+ explicit base on diff requests.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/vcs/base",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, vc_base_params.VcBaseParams),
+ ),
+ cast_to=VcBaseResponse,
+ )
+
+ def diff(
+ self,
+ *,
+ mode: VcsMode,
+ base: Optional[str] | Omit = omit,
+ context: Optional[int] | Omit = omit,
+ location: Optional[vc_diff_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VcDiffResponse:
+ """
+ Diff HEAD to the working copy (working), the base merge-base to the working copy
+ (branch), or the base merge-base to HEAD (committed). Omitting base preserves
+ repository-default comparison; supplying it overrides the comparison without
+ saving it.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/vcs/diff",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "mode": mode,
+ "base": base,
+ "context": context,
+ "location": location,
+ },
+ vc_diff_params.VcDiffParams,
+ ),
+ ),
+ cast_to=VcDiffResponse,
+ )
+
+ def get(
+ self,
+ *,
+ location: Optional[vc_get_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VcGetResponse:
+ """
+ Get current and default branch information for the requested location.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/vcs",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, vc_get_params.VcGetParams),
+ ),
+ cast_to=VcGetResponse,
+ )
+
+ def init(
+ self,
+ *,
+ location: Optional[vc_init_params.Location] | Omit = omit,
+ provider: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Initialize a repository using the selected VCS provider in a markerless
+ project's directory and refresh its location services. Omitting provider
+ defaults to git; built-in git and hg providers support initialization. An
+ unknown provider returns 400; a registered provider without init returns 501.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ "/api/vcs/init",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "location": location,
+ "provider": provider,
+ },
+ vc_init_params.VcInitParams,
+ ),
+ ),
+ cast_to=NoneType,
+ )
+
+ def status(
+ self,
+ *,
+ location: Optional[vc_status_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VcStatusResponse:
+ """
+ List uncommitted working-copy changes relative to the requested location.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/vcs/status",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, vc_status_params.VcStatusParams),
+ ),
+ cast_to=VcStatusResponse,
+ )
+
+
+class AsyncVcsResource(AsyncAPIResource):
+ @cached_property
+ def branch(self) -> AsyncBranchResource:
+ return AsyncBranchResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncVcsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
+ return AsyncVcsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncVcsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
+ return AsyncVcsResourceWithStreamingResponse(self)
+
+ async def base(
+ self,
+ *,
+ location: Optional[vc_base_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VcBaseResponse:
+ """
+ Infer a local review base from named branch creation history, or the repository
+ default only when currently on that branch. Returns null before the first commit
+ or when the provider lacks base metadata; ambiguous Git history requires an
+ explicit base on diff requests.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/vcs/base",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, vc_base_params.VcBaseParams),
+ ),
+ cast_to=VcBaseResponse,
+ )
+
+ async def diff(
+ self,
+ *,
+ mode: VcsMode,
+ base: Optional[str] | Omit = omit,
+ context: Optional[int] | Omit = omit,
+ location: Optional[vc_diff_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VcDiffResponse:
+ """
+ Diff HEAD to the working copy (working), the base merge-base to the working copy
+ (branch), or the base merge-base to HEAD (committed). Omitting base preserves
+ repository-default comparison; supplying it overrides the comparison without
+ saving it.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/vcs/diff",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "mode": mode,
+ "base": base,
+ "context": context,
+ "location": location,
+ },
+ vc_diff_params.VcDiffParams,
+ ),
+ ),
+ cast_to=VcDiffResponse,
+ )
+
+ async def get(
+ self,
+ *,
+ location: Optional[vc_get_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VcGetResponse:
+ """
+ Get current and default branch information for the requested location.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/vcs",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, vc_get_params.VcGetParams),
+ ),
+ cast_to=VcGetResponse,
+ )
+
+ async def init(
+ self,
+ *,
+ location: Optional[vc_init_params.Location] | Omit = omit,
+ provider: Optional[str] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Initialize a repository using the selected VCS provider in a markerless
+ project's directory and refresh its location services. Omitting provider
+ defaults to git; built-in git and hg providers support initialization. An
+ unknown provider returns 400; a registered provider without init returns 501.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ "/api/vcs/init",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "location": location,
+ "provider": provider,
+ },
+ vc_init_params.VcInitParams,
+ ),
+ ),
+ cast_to=NoneType,
+ )
+
+ async def status(
+ self,
+ *,
+ location: Optional[vc_status_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VcStatusResponse:
+ """
+ List uncommitted working-copy changes relative to the requested location.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/vcs/status",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, vc_status_params.VcStatusParams),
+ ),
+ cast_to=VcStatusResponse,
+ )
+
+
+class VcsResourceWithRawResponse:
+ def __init__(self, vcs: VcsResource) -> None:
+ self._vcs = vcs
+
+ self.base = to_raw_response_wrapper(
+ vcs.base,
+ )
+ self.diff = to_raw_response_wrapper(
+ vcs.diff,
+ )
+ self.get = to_raw_response_wrapper(
+ vcs.get,
+ )
+ self.init = to_raw_response_wrapper(
+ vcs.init,
+ )
+ self.status = to_raw_response_wrapper(
+ vcs.status,
+ )
+
+ @cached_property
+ def branch(self) -> BranchResourceWithRawResponse:
+ return BranchResourceWithRawResponse(self._vcs.branch)
+
+
+class AsyncVcsResourceWithRawResponse:
+ def __init__(self, vcs: AsyncVcsResource) -> None:
+ self._vcs = vcs
+
+ self.base = async_to_raw_response_wrapper(
+ vcs.base,
+ )
+ self.diff = async_to_raw_response_wrapper(
+ vcs.diff,
+ )
+ self.get = async_to_raw_response_wrapper(
+ vcs.get,
+ )
+ self.init = async_to_raw_response_wrapper(
+ vcs.init,
+ )
+ self.status = async_to_raw_response_wrapper(
+ vcs.status,
+ )
+
+ @cached_property
+ def branch(self) -> AsyncBranchResourceWithRawResponse:
+ return AsyncBranchResourceWithRawResponse(self._vcs.branch)
+
+
+class VcsResourceWithStreamingResponse:
+ def __init__(self, vcs: VcsResource) -> None:
+ self._vcs = vcs
+
+ self.base = to_streamed_response_wrapper(
+ vcs.base,
+ )
+ self.diff = to_streamed_response_wrapper(
+ vcs.diff,
+ )
+ self.get = to_streamed_response_wrapper(
+ vcs.get,
+ )
+ self.init = to_streamed_response_wrapper(
+ vcs.init,
+ )
+ self.status = to_streamed_response_wrapper(
+ vcs.status,
+ )
+
+ @cached_property
+ def branch(self) -> BranchResourceWithStreamingResponse:
+ return BranchResourceWithStreamingResponse(self._vcs.branch)
+
+
+class AsyncVcsResourceWithStreamingResponse:
+ def __init__(self, vcs: AsyncVcsResource) -> None:
+ self._vcs = vcs
+
+ self.base = async_to_streamed_response_wrapper(
+ vcs.base,
+ )
+ self.diff = async_to_streamed_response_wrapper(
+ vcs.diff,
+ )
+ self.get = async_to_streamed_response_wrapper(
+ vcs.get,
+ )
+ self.init = async_to_streamed_response_wrapper(
+ vcs.init,
+ )
+ self.status = async_to_streamed_response_wrapper(
+ vcs.status,
+ )
+
+ @cached_property
+ def branch(self) -> AsyncBranchResourceWithStreamingResponse:
+ return AsyncBranchResourceWithStreamingResponse(self._vcs.branch)
diff --git a/src/opencode/resources/voice.py b/src/opencode/resources/voice.py
index 4647041..6ca15e5 100644
--- a/src/opencode/resources/voice.py
+++ b/src/opencode/resources/voice.py
@@ -1,44 +1,47 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``voice`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from collections.abc import Mapping
-from functools import cached_property
-from typing import TYPE_CHECKING, Any, cast
+from typing import Dict, Optional
+
+import httpx
-from .._base_client import RequestOptions
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import voice_session_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- VoiceSessionResponseData,
- VoiceTransportInput,
- )
+from .._base_client import make_request_options
+from ..types.shared.voice_transport_input import VoiceTransportInput
+from ..types.shared.voice_session_response import VoiceSessionResponse
__all__ = ["VoiceResource", "AsyncVoiceResource"]
class VoiceResource(SyncAPIResource):
- """Operations under ``client.voice`` (OpenCode ``voice``)."""
-
@cached_property
def with_raw_response(self) -> VoiceResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return VoiceResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> VoiceResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return VoiceResourceWithStreamingResponse(self)
def session(
@@ -46,71 +49,62 @@ def session(
*,
model: str,
transport: VoiceTransportInput,
- session: Mapping[str, Any] | NotGiven | None = NOT_GIVEN,
+ session: Optional[Dict[str, object]] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> VoiceSessionResponseData:
- """Create voice session.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VoiceSessionResponse:
+ """
+ Create a server-brokered voice session using the server's configured provider
+ credentials and return the WebRTC SDP answer or WebSocket ephemeral token.
- Create a server-brokered voice session using the server's configured provider credentials and return the WebRTC SDP answer or WebSocket ephemeral token.
+ Args:
+ extra_headers: Send extra headers
- This endpoint is experimental in OpenCode and may change without notice.
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
+ extra_body: Add additional JSON properties to the request
- ``POST /api/experimental/voice/session``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "VoiceSessionResponseData",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/experimental/voice/session",
- body={"model": model, "session": session, "transport": transport},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="VoiceSessionResponseData",
- unwrap=True,
+ return self._post(
+ "/api/experimental/voice/session",
+ body=maybe_transform(
+ {
+ "model": model,
+ "transport": transport,
+ "session": session,
+ },
+ voice_session_params.VoiceSessionParams,
),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VoiceSessionResponse,
)
-class VoiceResourceWithRawResponse:
- """``client.voice.with_raw_response``."""
-
- def __init__(self, resource: VoiceResource) -> None:
- self._resource = resource
- self.session = to_raw_response_wrapper(resource.session)
-
-
-class VoiceResourceWithStreamingResponse:
- """``client.voice.with_streaming_response``."""
-
- def __init__(self, resource: VoiceResource) -> None:
- self._resource = resource
- self.session = to_streamed_response_wrapper(resource.session)
-
-
class AsyncVoiceResource(AsyncAPIResource):
- """Operations under ``client.voice`` (OpenCode ``voice``)."""
-
@cached_property
def with_raw_response(self) -> AsyncVoiceResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncVoiceResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncVoiceResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncVoiceResourceWithStreamingResponse(self)
async def session(
@@ -118,55 +112,75 @@ async def session(
*,
model: str,
transport: VoiceTransportInput,
- session: Mapping[str, Any] | NotGiven | None = NOT_GIVEN,
+ session: Optional[Dict[str, object]] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> VoiceSessionResponseData:
- """Create voice session.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VoiceSessionResponse:
+ """
+ Create a server-brokered voice session using the server's configured provider
+ credentials and return the WebRTC SDP answer or WebSocket ephemeral token.
- Create a server-brokered voice session using the server's configured provider credentials and return the WebRTC SDP answer or WebSocket ephemeral token.
+ Args:
+ extra_headers: Send extra headers
- This endpoint is experimental in OpenCode and may change without notice.
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
+ extra_body: Add additional JSON properties to the request
- ``POST /api/experimental/voice/session``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "VoiceSessionResponseData",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/experimental/voice/session",
- body={"model": model, "session": session, "transport": transport},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="VoiceSessionResponseData",
- unwrap=True,
+ return await self._post(
+ "/api/experimental/voice/session",
+ body=await async_maybe_transform(
+ {
+ "model": model,
+ "transport": transport,
+ "session": session,
+ },
+ voice_session_params.VoiceSessionParams,
),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VoiceSessionResponse,
+ )
+
+
+class VoiceResourceWithRawResponse:
+ def __init__(self, voice: VoiceResource) -> None:
+ self._voice = voice
+
+ self.session = to_raw_response_wrapper(
+ voice.session,
)
class AsyncVoiceResourceWithRawResponse:
- """``client.voice.with_raw_response``."""
+ def __init__(self, voice: AsyncVoiceResource) -> None:
+ self._voice = voice
- def __init__(self, resource: AsyncVoiceResource) -> None:
- self._resource = resource
- self.session = async_to_raw_response_wrapper(resource.session)
+ self.session = async_to_raw_response_wrapper(
+ voice.session,
+ )
+
+
+class VoiceResourceWithStreamingResponse:
+ def __init__(self, voice: VoiceResource) -> None:
+ self._voice = voice
+
+ self.session = to_streamed_response_wrapper(
+ voice.session,
+ )
class AsyncVoiceResourceWithStreamingResponse:
- """``client.voice.with_streaming_response``."""
+ def __init__(self, voice: AsyncVoiceResource) -> None:
+ self._voice = voice
- def __init__(self, resource: AsyncVoiceResource) -> None:
- self._resource = resource
- self.session = async_to_streamed_response_wrapper(resource.session)
+ self.session = async_to_streamed_response_wrapper(
+ voice.session,
+ )
diff --git a/src/opencode/resources/websearch.py b/src/opencode/resources/websearch.py
index e245a42..b45fd98 100644
--- a/src/opencode/resources/websearch.py
+++ b/src/opencode/resources/websearch.py
@@ -1,247 +1,277 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``websearch`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
+from typing import Optional
+
+import httpx
-from .._base_client import RequestOptions
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import websearch_query_params, websearch_providers_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- Location,
- WebSearchID,
- WebsearchProvidersResponse,
- WebsearchQueryResponse,
- )
+from .._base_client import make_request_options
+from ..types.shared.web_search_id import WebSearchID
+from ..types.websearch_query_response import WebsearchQueryResponse
+from ..types.websearch_providers_response import WebsearchProvidersResponse
__all__ = ["WebsearchResource", "AsyncWebsearchResource"]
class WebsearchResource(SyncAPIResource):
- """Operations under ``client.websearch`` (OpenCode ``websearch``)."""
-
@cached_property
def with_raw_response(self) -> WebsearchResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return WebsearchResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> WebsearchResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return WebsearchResourceWithStreamingResponse(self)
def providers(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[websearch_providers_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WebsearchProvidersResponse:
- """List web search providers.
-
+ """
Return the registered web search providers.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/websearch/provider``
- """
- return cast(
- "WebsearchProvidersResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/websearch/provider",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="WebsearchProvidersResponse",
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/api/websearch/provider",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, websearch_providers_params.WebsearchProvidersParams),
),
+ cast_to=WebsearchProvidersResponse,
)
def query(
self,
*,
query: str,
- location: Location | NotGiven | None = NOT_GIVEN,
- provider_id: WebSearchID | NotGiven = NOT_GIVEN,
+ location: Optional[websearch_query_params.Location] | Omit = omit,
+ provider_id: WebSearchID | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WebsearchQueryResponse:
- """Search the web.
-
- Run one web search through the selected provider. Specify a provider to override the configured default.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``POST /api/websearch``
- """
- return cast(
- "WebsearchQueryResponse",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/websearch",
- query={"location": self._location(location)},
- body={"query": query, "providerID": provider_id},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="WebsearchQueryResponse",
- ),
- )
-
-
-class WebsearchResourceWithRawResponse:
- """``client.websearch.with_raw_response``."""
+ """
+ Run one web search through the selected provider. Specify a provider to override
+ the configured default.
- def __init__(self, resource: WebsearchResource) -> None:
- self._resource = resource
- self.providers = to_raw_response_wrapper(resource.providers)
- self.query = to_raw_response_wrapper(resource.query)
+ Args:
+ extra_headers: Send extra headers
+ extra_query: Add additional query parameters to the request
-class WebsearchResourceWithStreamingResponse:
- """``client.websearch.with_streaming_response``."""
+ extra_body: Add additional JSON properties to the request
- def __init__(self, resource: WebsearchResource) -> None:
- self._resource = resource
- self.providers = to_streamed_response_wrapper(resource.providers)
- self.query = to_streamed_response_wrapper(resource.query)
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/api/websearch",
+ body=maybe_transform(
+ {
+ "query": query,
+ "provider_id": provider_id,
+ },
+ websearch_query_params.WebsearchQueryParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"location": location}, websearch_query_params.WebsearchQueryParams),
+ ),
+ cast_to=WebsearchQueryResponse,
+ )
class AsyncWebsearchResource(AsyncAPIResource):
- """Operations under ``client.websearch`` (OpenCode ``websearch``)."""
-
@cached_property
def with_raw_response(self) -> AsyncWebsearchResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncWebsearchResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncWebsearchResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncWebsearchResourceWithStreamingResponse(self)
async def providers(
self,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Optional[websearch_providers_params.Location] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WebsearchProvidersResponse:
- """List web search providers.
-
+ """
Return the registered web search providers.
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/websearch/provider``
- """
- return cast(
- "WebsearchProvidersResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/websearch/provider",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/api/websearch/provider",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"location": location}, websearch_providers_params.WebsearchProvidersParams
),
- response_type="WebsearchProvidersResponse",
),
+ cast_to=WebsearchProvidersResponse,
)
async def query(
self,
*,
query: str,
- location: Location | NotGiven | None = NOT_GIVEN,
- provider_id: WebSearchID | NotGiven = NOT_GIVEN,
+ location: Optional[websearch_query_params.Location] | Omit = omit,
+ provider_id: WebSearchID | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WebsearchQueryResponse:
- """Search the web.
-
- Run one web search through the selected provider. Specify a provider to override the configured default.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``POST /api/websearch``
- """
- return cast(
- "WebsearchQueryResponse",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/websearch",
- query={"location": self._location(location)},
- body={"query": query, "providerID": provider_id},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="WebsearchQueryResponse",
+ """
+ Run one web search through the selected provider. Specify a provider to override
+ the configured default.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/api/websearch",
+ body=await async_maybe_transform(
+ {
+ "query": query,
+ "provider_id": provider_id,
+ },
+ websearch_query_params.WebsearchQueryParams,
),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"location": location}, websearch_query_params.WebsearchQueryParams),
+ ),
+ cast_to=WebsearchQueryResponse,
+ )
+
+
+class WebsearchResourceWithRawResponse:
+ def __init__(self, websearch: WebsearchResource) -> None:
+ self._websearch = websearch
+
+ self.providers = to_raw_response_wrapper(
+ websearch.providers,
+ )
+ self.query = to_raw_response_wrapper(
+ websearch.query,
)
class AsyncWebsearchResourceWithRawResponse:
- """``client.websearch.with_raw_response``."""
+ def __init__(self, websearch: AsyncWebsearchResource) -> None:
+ self._websearch = websearch
- def __init__(self, resource: AsyncWebsearchResource) -> None:
- self._resource = resource
- self.providers = async_to_raw_response_wrapper(resource.providers)
- self.query = async_to_raw_response_wrapper(resource.query)
+ self.providers = async_to_raw_response_wrapper(
+ websearch.providers,
+ )
+ self.query = async_to_raw_response_wrapper(
+ websearch.query,
+ )
+
+
+class WebsearchResourceWithStreamingResponse:
+ def __init__(self, websearch: WebsearchResource) -> None:
+ self._websearch = websearch
+
+ self.providers = to_streamed_response_wrapper(
+ websearch.providers,
+ )
+ self.query = to_streamed_response_wrapper(
+ websearch.query,
+ )
class AsyncWebsearchResourceWithStreamingResponse:
- """``client.websearch.with_streaming_response``."""
+ def __init__(self, websearch: AsyncWebsearchResource) -> None:
+ self._websearch = websearch
- def __init__(self, resource: AsyncWebsearchResource) -> None:
- self._resource = resource
- self.providers = async_to_streamed_response_wrapper(resource.providers)
- self.query = async_to_streamed_response_wrapper(resource.query)
+ self.providers = async_to_streamed_response_wrapper(
+ websearch.providers,
+ )
+ self.query = async_to_streamed_response_wrapper(
+ websearch.query,
+ )
diff --git a/src/opencode/resources/worktree.py b/src/opencode/resources/worktree.py
index d44b906..041d9cf 100644
--- a/src/opencode/resources/worktree.py
+++ b/src/opencode/resources/worktree.py
@@ -1,419 +1,456 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``worktree`` resource."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
+import httpx
-from .._base_client import RequestOptions
-from .._resource import AsyncAPIResource, SyncAPIResource
+from ..types import worktree_list_params, worktree_create_params, worktree_remove_params, worktree_refresh_params
+from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from .._utils import maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
to_raw_response_wrapper,
to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- AbsolutePath,
- ProjectID,
- WorktreeInfo,
- WorktreeList,
- )
+from .._base_client import make_request_options
+from ..types.shared.project_id import ProjectID
+from ..types.shared.absolute_path import AbsolutePath
+from ..types.shared.worktree_info import WorktreeInfo
+from ..types.shared.worktree_list import WorktreeList
__all__ = ["WorktreeResource", "AsyncWorktreeResource"]
class WorktreeResource(SyncAPIResource):
- """Operations under ``client.worktree`` (OpenCode ``worktree``)."""
-
@cached_property
def with_raw_response(self) -> WorktreeResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return WorktreeResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> WorktreeResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return WorktreeResourceWithStreamingResponse(self)
- def list(
+ def create(
self,
*,
project_id: ProjectID,
+ branch: str | Omit = omit,
+ directory: AbsolutePath | Omit = omit,
+ from_: AbsolutePath | Omit = omit,
+ name: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> WorktreeList:
- """List worktrees.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> WorktreeInfo:
+ """
+ Load the project's canonical configuration, create a local worktree using its
+ selected strategy, then run the project's setup script.
- Return the project's saved worktree inventory without loading configuration or running discovery.
+ Args:
+ extra_headers: Send extra headers
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ProjectNotFoundError: The server answered 404.
+ extra_query: Add additional query parameters to the request
- ``GET /api/worktree``
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "WorktreeList",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/worktree",
- query={"projectID": project_id},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="WorktreeList",
+ return self._post(
+ "/api/worktree",
+ body=maybe_transform(
+ {
+ "project_id": project_id,
+ "branch": branch,
+ "directory": directory,
+ "from_": from_,
+ "name": name,
+ },
+ worktree_create_params.WorktreeCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=WorktreeInfo,
)
- def create(
+ def list(
self,
*,
project_id: ProjectID,
- from_: AbsolutePath | NotGiven = NOT_GIVEN,
- branch: str | NotGiven = NOT_GIVEN,
- directory: AbsolutePath | NotGiven = NOT_GIVEN,
- name: str | NotGiven = NOT_GIVEN,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> WorktreeInfo:
- """Create worktree.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> WorktreeList:
+ """
+ Return the project's saved worktree inventory without loading configuration or
+ running discovery.
+
+ Args:
+ extra_headers: Send extra headers
- Load the project's canonical configuration, create a local worktree using its selected strategy, then run the project's setup script.
+ extra_query: Add additional query parameters to the request
- Raises:
- WorktreeError: The server answered 400.
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ProjectNotFoundError: The server answered 404.
+ extra_body: Add additional JSON properties to the request
- ``POST /api/worktree``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "WorktreeInfo",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/worktree",
- body={
- "projectID": project_id,
- "from": from_,
- "branch": branch,
- "directory": directory,
- "name": name,
- },
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="WorktreeInfo",
+ return self._get(
+ "/api/worktree",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"project_id": project_id}, worktree_list_params.WorktreeListParams),
),
+ cast_to=WorktreeList,
)
- def remove(
+ def refresh(
self,
*,
project_id: ProjectID,
- directory: AbsolutePath,
- force: bool,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Remove worktree.
+ """
+ Load the project's canonical configuration, discover worktrees across known
+ checkout roots using all available strategies, and reconcile saved state.
- Load the project's canonical configuration and remove a saved worktree using its recorded strategy.
+ Args:
+ extra_headers: Send extra headers
- Raises:
- WorktreeError: The server answered 400.
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ProjectNotFoundError: The server answered 404.
+ extra_query: Add additional query parameters to the request
- ``DELETE /api/worktree``
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="DELETE",
- path="/api/worktree",
- body={"projectID": project_id, "directory": directory, "force": force},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ "/api/worktree/refresh",
+ body=maybe_transform({"project_id": project_id}, worktree_refresh_params.WorktreeRefreshParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=NoneType,
)
- def refresh(
+ def remove(
self,
*,
+ directory: AbsolutePath,
+ force: bool,
project_id: ProjectID,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Refresh worktrees.
+ """
+ Load the project's canonical configuration and remove a saved worktree using its
+ recorded strategy.
- Load the project's canonical configuration, discover worktrees across known checkout roots using all available strategies, and reconcile saved state.
+ Args:
+ extra_headers: Send extra headers
- Raises:
- WorktreeError: The server answered 400.
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ProjectNotFoundError: The server answered 404.
+ extra_query: Add additional query parameters to the request
- ``POST /api/worktree/refresh``
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/worktree/refresh",
- body={"projectID": project_id},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ "/api/worktree",
+ body=maybe_transform(
+ {
+ "directory": directory,
+ "force": force,
+ "project_id": project_id,
+ },
+ worktree_remove_params.WorktreeRemoveParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=NoneType,
)
-class WorktreeResourceWithRawResponse:
- """``client.worktree.with_raw_response``."""
-
- def __init__(self, resource: WorktreeResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
- self.create = to_raw_response_wrapper(resource.create)
- self.remove = to_raw_response_wrapper(resource.remove)
- self.refresh = to_raw_response_wrapper(resource.refresh)
-
-
-class WorktreeResourceWithStreamingResponse:
- """``client.worktree.with_streaming_response``."""
-
- def __init__(self, resource: WorktreeResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
- self.create = to_streamed_response_wrapper(resource.create)
- self.remove = to_streamed_response_wrapper(resource.remove)
- self.refresh = to_streamed_response_wrapper(resource.refresh)
-
-
class AsyncWorktreeResource(AsyncAPIResource):
- """Operations under ``client.worktree`` (OpenCode ``worktree``)."""
-
@cached_property
def with_raw_response(self) -> AsyncWorktreeResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/#accessing-raw-response-data-eg-headers
+ """
return AsyncWorktreeResourceWithRawResponse(self)
@cached_property
def with_streaming_response(self) -> AsyncWorktreeResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/#with_streaming_response
+ """
return AsyncWorktreeResourceWithStreamingResponse(self)
- async def list(
+ async def create(
self,
*,
project_id: ProjectID,
+ branch: str | Omit = omit,
+ directory: AbsolutePath | Omit = omit,
+ from_: AbsolutePath | Omit = omit,
+ name: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> WorktreeList:
- """List worktrees.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> WorktreeInfo:
+ """
+ Load the project's canonical configuration, create a local worktree using its
+ selected strategy, then run the project's setup script.
+
+ Args:
+ extra_headers: Send extra headers
- Return the project's saved worktree inventory without loading configuration or running discovery.
+ extra_query: Add additional query parameters to the request
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ProjectNotFoundError: The server answered 404.
+ extra_body: Add additional JSON properties to the request
- ``GET /api/worktree``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "WorktreeList",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/worktree",
- query={"projectID": project_id},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="WorktreeList",
+ return await self._post(
+ "/api/worktree",
+ body=await async_maybe_transform(
+ {
+ "project_id": project_id,
+ "branch": branch,
+ "directory": directory,
+ "from_": from_,
+ "name": name,
+ },
+ worktree_create_params.WorktreeCreateParams,
),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=WorktreeInfo,
)
- async def create(
+ async def list(
self,
*,
project_id: ProjectID,
- from_: AbsolutePath | NotGiven = NOT_GIVEN,
- branch: str | NotGiven = NOT_GIVEN,
- directory: AbsolutePath | NotGiven = NOT_GIVEN,
- name: str | NotGiven = NOT_GIVEN,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> WorktreeInfo:
- """Create worktree.
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> WorktreeList:
+ """
+ Return the project's saved worktree inventory without loading configuration or
+ running discovery.
- Load the project's canonical configuration, create a local worktree using its selected strategy, then run the project's setup script.
+ Args:
+ extra_headers: Send extra headers
- Raises:
- WorktreeError: The server answered 400.
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ProjectNotFoundError: The server answered 404.
+ extra_query: Add additional query parameters to the request
- ``POST /api/worktree``
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "WorktreeInfo",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/worktree",
- body={
- "projectID": project_id,
- "from": from_,
- "branch": branch,
- "directory": directory,
- "name": name,
- },
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="WorktreeInfo",
+ return await self._get(
+ "/api/worktree",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform({"project_id": project_id}, worktree_list_params.WorktreeListParams),
),
+ cast_to=WorktreeList,
)
- async def remove(
+ async def refresh(
self,
*,
project_id: ProjectID,
- directory: AbsolutePath,
- force: bool,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Remove worktree.
+ """
+ Load the project's canonical configuration, discover worktrees across known
+ checkout roots using all available strategies, and reconcile saved state.
+
+ Args:
+ extra_headers: Send extra headers
- Load the project's canonical configuration and remove a saved worktree using its recorded strategy.
+ extra_query: Add additional query parameters to the request
- Raises:
- WorktreeError: The server answered 400.
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ProjectNotFoundError: The server answered 404.
+ extra_body: Add additional JSON properties to the request
- ``DELETE /api/worktree``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="DELETE",
- path="/api/worktree",
- body={"projectID": project_id, "directory": directory, "force": force},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ "/api/worktree/refresh",
+ body=await async_maybe_transform({"project_id": project_id}, worktree_refresh_params.WorktreeRefreshParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
+ cast_to=NoneType,
)
- async def refresh(
+ async def remove(
self,
*,
+ directory: AbsolutePath,
+ force: bool,
project_id: ProjectID,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
extra_query: Query | None = None,
extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Refresh worktrees.
+ """
+ Load the project's canonical configuration and remove a saved worktree using its
+ recorded strategy.
+
+ Args:
+ extra_headers: Send extra headers
- Load the project's canonical configuration, discover worktrees across known checkout roots using all available strategies, and reconcile saved state.
+ extra_query: Add additional query parameters to the request
- Raises:
- WorktreeError: The server answered 400.
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ProjectNotFoundError: The server answered 404.
+ extra_body: Add additional JSON properties to the request
- ``POST /api/worktree/refresh``
+ timeout: Override the client-level default timeout for this request, in seconds
"""
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/worktree/refresh",
- body={"projectID": project_id},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ "/api/worktree",
+ body=await async_maybe_transform(
+ {
+ "directory": directory,
+ "force": force,
+ "project_id": project_id,
+ },
+ worktree_remove_params.WorktreeRemoveParams,
),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class WorktreeResourceWithRawResponse:
+ def __init__(self, worktree: WorktreeResource) -> None:
+ self._worktree = worktree
+
+ self.create = to_raw_response_wrapper(
+ worktree.create,
+ )
+ self.list = to_raw_response_wrapper(
+ worktree.list,
+ )
+ self.refresh = to_raw_response_wrapper(
+ worktree.refresh,
+ )
+ self.remove = to_raw_response_wrapper(
+ worktree.remove,
)
class AsyncWorktreeResourceWithRawResponse:
- """``client.worktree.with_raw_response``."""
+ def __init__(self, worktree: AsyncWorktreeResource) -> None:
+ self._worktree = worktree
+
+ self.create = async_to_raw_response_wrapper(
+ worktree.create,
+ )
+ self.list = async_to_raw_response_wrapper(
+ worktree.list,
+ )
+ self.refresh = async_to_raw_response_wrapper(
+ worktree.refresh,
+ )
+ self.remove = async_to_raw_response_wrapper(
+ worktree.remove,
+ )
+
- def __init__(self, resource: AsyncWorktreeResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
- self.create = async_to_raw_response_wrapper(resource.create)
- self.remove = async_to_raw_response_wrapper(resource.remove)
- self.refresh = async_to_raw_response_wrapper(resource.refresh)
+class WorktreeResourceWithStreamingResponse:
+ def __init__(self, worktree: WorktreeResource) -> None:
+ self._worktree = worktree
+
+ self.create = to_streamed_response_wrapper(
+ worktree.create,
+ )
+ self.list = to_streamed_response_wrapper(
+ worktree.list,
+ )
+ self.refresh = to_streamed_response_wrapper(
+ worktree.refresh,
+ )
+ self.remove = to_streamed_response_wrapper(
+ worktree.remove,
+ )
class AsyncWorktreeResourceWithStreamingResponse:
- """``client.worktree.with_streaming_response``."""
-
- def __init__(self, resource: AsyncWorktreeResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.create = async_to_streamed_response_wrapper(resource.create)
- self.remove = async_to_streamed_response_wrapper(resource.remove)
- self.refresh = async_to_streamed_response_wrapper(resource.refresh)
+ def __init__(self, worktree: AsyncWorktreeResource) -> None:
+ self._worktree = worktree
+
+ self.create = async_to_streamed_response_wrapper(
+ worktree.create,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ worktree.list,
+ )
+ self.refresh = async_to_streamed_response_wrapper(
+ worktree.refresh,
+ )
+ self.remove = async_to_streamed_response_wrapper(
+ worktree.remove,
+ )
diff --git a/src/opencode/types/__init__.py b/src/opencode/types/__init__.py
index aea5557..a596357 100644
--- a/src/opencode/types/__init__.py
+++ b/src/opencode/types/__init__.py
@@ -1,1462 +1,498 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""Typed models for OpenCode API requests, responses and events."""
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
from __future__ import annotations
-from ._generated import (
- AbsolutePath as AbsolutePath,
- AgentColor as AgentColor,
- AgentGetResponse as AgentGetResponse,
+from .shared import (
+ Pty as Pty,
+ Event as Event,
+ PtyID as PtyID,
+ FormID as FormID,
AgentID as AgentID,
+ EventID as EventID,
+ Project as Project,
+ ShellID as ShellID,
+ SkillID as SkillID,
+ VcsBase as VcsBase,
+ VcsInfo as VcsInfo,
+ VcsMode as VcsMode,
+ EventSeq as EventSeq,
+ FormInfo as FormInfo,
+ FormWhen as FormWhen,
+ ModelRef as ModelRef,
+ MoneyUSD as MoneyUSD,
+ RpcEvent as RpcEvent,
AgentInfo as AgentInfo,
- AgentListResponse as AgentListResponse,
- AgentNotFoundErrorBody as AgentNotFoundErrorBody,
- AgentUpdatedEvent as AgentUpdatedEvent,
- CommandExecutionErrorBody as CommandExecutionErrorBody,
+ FormField as FormField,
+ FormState as FormState,
+ FormValue as FormValue,
+ McpServer as McpServer,
+ ModelCost as ModelCost,
+ ModelInfo as ModelInfo,
+ ProjectID as ProjectID,
+ RpcOutput as RpcOutput,
+ SessionID as SessionID,
+ ShellInfo as ShellInfo,
+ SkillInfo as SkillInfo,
+ SkillName as SkillName,
+ VcsBranch as VcsBranch,
+ AgentColor as AgentColor,
+ ConfigInfo as ConfigInfo,
+ FormAnswer as FormAnswer,
+ FormDetail as FormDetail,
+ FormFields as FormFields,
+ FormOption as FormOption,
+ PluginInfo as PluginInfo,
+ ProjectVcs as ProjectVcs,
+ ProviderID as ProviderID,
+ ServerInfo as ServerInfo,
+ SessionID1 as SessionID1,
+ SnapshotID as SnapshotID,
CommandInfo as CommandInfo,
- CommandListResponse as CommandListResponse,
- CommandNotFoundErrorBody as CommandNotFoundErrorBody,
- CommandUpdatedEvent as CommandUpdatedEvent,
ConfigAgent as ConfigAgent,
- ConfigAgentModel as ConfigAgentModel,
- ConfigAgentRequest as ConfigAgentRequest,
- ConfigCommand as ConfigCommand,
- ConfigCommandModel as ConfigCommandModel,
- ConfigDirectory as ConfigDirectory,
- ConfigDocument as ConfigDocument,
ConfigEntry as ConfigEntry,
- ConfigFormatterEntry as ConfigFormatterEntry,
- ConfigInfo as ConfigInfo,
- ConfigInfoCompaction as ConfigInfoCompaction,
- ConfigInfoCompactionKeep as ConfigInfoCompactionKeep,
- ConfigInfoEnterprise as ConfigInfoEnterprise,
- ConfigInfoExperimental as ConfigInfoExperimental,
- ConfigInfoExperimentalPoliciesItem as ConfigInfoExperimentalPoliciesItem,
- ConfigInfoLspOption2Value as ConfigInfoLspOption2Value,
- ConfigInfoMcp as ConfigInfoMcp,
- ConfigInfoMcpServersValue as ConfigInfoMcpServersValue,
- ConfigInfoMcpTimeout as ConfigInfoMcpTimeout,
- ConfigInfoMedia as ConfigInfoMedia,
- ConfigInfoMediaImage as ConfigInfoMediaImage,
- ConfigInfoModel as ConfigInfoModel,
- ConfigInfoPoliciesItem as ConfigInfoPoliciesItem,
- ConfigInfoToolOutput as ConfigInfoToolOutput,
- ConfigInfoWatcher as ConfigInfoWatcher,
- ConfigLSPServer as ConfigLSPServer,
ConfigModel as ConfigModel,
- ConfigModelCapabilities as ConfigModelCapabilities,
- ConfigModelCost as ConfigModelCost,
- ConfigModelCostCache as ConfigModelCostCache,
- ConfigModelCostTier as ConfigModelCostTier,
- ConfigModelLimit as ConfigModelLimit,
- ConfigModelReasoning as ConfigModelReasoning,
- ConfigModelReasoningFalse as ConfigModelReasoningFalse,
- ConfigModelReasoningTrue as ConfigModelReasoningTrue,
- ConfigModelReasoningTrueBudget as ConfigModelReasoningTrueBudget,
- ConfigModelSettings as ConfigModelSettings,
- ConfigModelVariantsItem as ConfigModelVariantsItem,
- ConfigPluginEntry as ConfigPluginEntry,
- ConfigProvider as ConfigProvider,
- ConfigProviderSettings as ConfigProviderSettings,
- ConfigReferenceGit as ConfigReferenceGit,
- ConfigReferenceLocal as ConfigReferenceLocal,
- ConfigShellOption as ConfigShellOption,
- ConfigUpdatedEvent as ConfigUpdatedEvent,
+ FormFields1 as FormFields1,
+ FormFields2 as FormFields2,
+ FormFields3 as FormFields3,
+ LocationRef as LocationRef,
+ McpProtocol as McpProtocol,
+ McpResource as McpResource,
+ PairingCode as PairingCode,
+ PluginState as PluginState,
+ ProjectIcon as ProjectIcon,
+ ProjectTime as ProjectTime,
+ SessionInfo as SessionInfo,
+ ToolContent as ToolContent,
+ WebSearchID as WebSearchID,
+ WorkspaceID as WorkspaceID,
+ AbsolutePath as AbsolutePath,
+ CredentialID as CredentialID,
+ EventVersion as EventVersion,
+ FileDiffInfo as FileDiffInfo,
+ FormMetadata as FormMetadata,
+ ModelVariant as ModelVariant,
+ PermissionID as PermissionID,
+ PluginSource as PluginSource,
+ PromptBase64 as PromptBase64,
+ ProviderInfo as ProviderInfo,
+ RelativePath as RelativePath,
+ WorktreeInfo as WorktreeInfo,
+ WorktreeList as WorktreeList,
+ ConfigCommand as ConfigCommand,
ConfigWarming as ConfigWarming,
- ConfigWebSearchInfo as ConfigWebSearchInfo,
+ CredentialKey as CredentialKey,
+ IntegrationID as IntegrationID,
+ ModelSettings as ModelSettings,
+ PromptMention as PromptMention,
+ ReferenceInfo as ReferenceInfo,
+ SessionRevert as SessionRevert,
+ SessionStatus as SessionStatus,
+ VcsBranchList as VcsBranchList,
+ VcsFileStatus as VcsFileStatus,
+ ConfigDocument as ConfigDocument,
+ ConfigProvider as ConfigProvider,
ConfigWorktree as ConfigWorktree,
- ConflictErrorBody as ConflictErrorBody,
- ConnectionCredentialInfo as ConnectionCredentialInfo,
- ConnectionEnvInfo as ConnectionEnvInfo,
ConnectionInfo as ConnectionInfo,
- ConnectionStatus as ConnectionStatus,
+ EventVersion10 as EventVersion10,
+ McpLocalConfig as McpLocalConfig,
+ McpOAuthConfig as McpOAuthConfig,
+ PairingSession as PairingSession,
+ PermissionRule as PermissionRule,
+ PluginFeatures as PluginFeatures,
+ PtyExitedEvent as PtyExitedEvent,
+ SessionsCursor as SessionsCursor,
+ TokenUsageInfo as TokenUsageInfo,
+ VoiceTransport as VoiceTransport,
+ ConfigDirectory as ConfigDirectory,
+ ConfigLSPServer as ConfigLSPServer,
+ ConfigModelCost as ConfigModelCost,
CredentialEntry as CredentialEntry,
- CredentialExternal as CredentialExternal,
- CredentialID as CredentialID,
- CredentialKey as CredentialKey,
CredentialOAuth as CredentialOAuth,
- CredentialSwitchedEvent as CredentialSwitchedEvent,
- CredentialSwitchedEventData as CredentialSwitchedEventData,
- CredentialUpdatedEvent as CredentialUpdatedEvent,
CredentialValue as CredentialValue,
- Event as Event,
- EventID as EventID,
- EventLogSynced as EventLogSynced,
- EventSeq as EventSeq,
- EventVersion as EventVersion,
- EventVersion10 as EventVersion10,
- ExperimentalPersistentPtyCreateSize as ExperimentalPersistentPtyCreateSize,
- ExperimentalPersistentPtyHandoffResponse as ExperimentalPersistentPtyHandoffResponse,
- ExperimentalPersistentPtyUpdateSize as ExperimentalPersistentPtyUpdateSize,
- FileDiffInfo as FileDiffInfo,
- FileFindResponse as FileFindResponse,
- FileListResponse as FileListResponse,
- FileNotFoundErrorBody as FileNotFoundErrorBody,
- FilesystemChangedEvent as FilesystemChangedEvent,
- FilesystemChangedEventData as FilesystemChangedEventData,
FileSystemEntry as FileSystemEntry,
FileSystemWrite as FileSystemWrite,
- FileWriteResponse as FileWriteResponse,
- ForbiddenErrorBody as ForbiddenErrorBody,
- FormAlreadySettledErrorBody as FormAlreadySettledErrorBody,
- FormAnswer as FormAnswer,
- FormBooleanField as FormBooleanField,
- FormCancelledEvent as FormCancelledEvent,
- FormCancelledEventData as FormCancelledEventData,
- FormCreatedEvent as FormCreatedEvent,
- FormCreatedEventData as FormCreatedEventData,
- FormDetail as FormDetail,
- FormExternalField as FormExternalField,
- FormField as FormField,
- FormFields as FormFields,
- FormID as FormID,
- FormInfo as FormInfo,
- FormIntegerField as FormIntegerField,
- FormInvalidAnswerErrorBody as FormInvalidAnswerErrorBody,
- FormListResponse as FormListResponse,
- FormMetadata as FormMetadata,
- FormMultiselectField as FormMultiselectField,
- FormNotFoundErrorBody as FormNotFoundErrorBody,
FormNumberField as FormNumberField,
- FormOption as FormOption,
- FormRepliedEvent as FormRepliedEvent,
- FormRepliedEventData as FormRepliedEventData,
- FormState as FormState,
- FormStateAnswered as FormStateAnswered,
- FormStateCancelled as FormStateCancelled,
- FormStatePending as FormStatePending,
FormStringField as FormStringField,
- FormValue as FormValue,
- FormWhen as FormWhen,
- GenerateTextResponse as GenerateTextResponse,
- GenerateTextResponseData as GenerateTextResponseData,
- InstallationUpdateAvailableEvent as InstallationUpdateAvailableEvent,
- InstallationUpdateAvailableEventData as InstallationUpdateAvailableEventData,
- InstallationUpdatedEvent as InstallationUpdatedEvent,
- InstallationUpdatedEventData as InstallationUpdatedEventData,
- InstructionEntryInfo as InstructionEntryInfo,
- InstructionEntryKey as InstructionEntryKey,
- InstructionEntrySnapshot as InstructionEntrySnapshot,
- InstructionEntrySnapshotItem as InstructionEntrySnapshotItem,
- InstructionEntryValueTooLargeErrorBody as InstructionEntryValueTooLargeErrorBody,
InstructionHash as InstructionHash,
- InstructionKey as InstructionKey,
- IntegrationAttempt as IntegrationAttempt,
- IntegrationAttemptID as IntegrationAttemptID,
- IntegrationAttemptNotFoundErrorBody as IntegrationAttemptNotFoundErrorBody,
- IntegrationAttemptStatus as IntegrationAttemptStatus,
- IntegrationAttemptStatusComplete as IntegrationAttemptStatusComplete,
- IntegrationAttemptStatusCompleteTime as IntegrationAttemptStatusCompleteTime,
- IntegrationAttemptStatusExpired as IntegrationAttemptStatusExpired,
- IntegrationAttemptStatusExpiredTime as IntegrationAttemptStatusExpiredTime,
- IntegrationAttemptStatusFailed as IntegrationAttemptStatusFailed,
- IntegrationAttemptStatusFailedTime as IntegrationAttemptStatusFailedTime,
- IntegrationAttemptStatusPending as IntegrationAttemptStatusPending,
- IntegrationAttemptStatusPendingTime as IntegrationAttemptStatusPendingTime,
- IntegrationAttemptTime as IntegrationAttemptTime,
- IntegrationCommandAttempt as IntegrationCommandAttempt,
- IntegrationCommandAttemptStatus as IntegrationCommandAttemptStatus,
- IntegrationCommandAttemptStatusComplete as IntegrationCommandAttemptStatusComplete,
- IntegrationCommandAttemptStatusCompleteTime as IntegrationCommandAttemptStatusCompleteTime,
- IntegrationCommandAttemptStatusExpired as IntegrationCommandAttemptStatusExpired,
- IntegrationCommandAttemptStatusExpiredTime as IntegrationCommandAttemptStatusExpiredTime,
- IntegrationCommandAttemptStatusFailed as IntegrationCommandAttemptStatusFailed,
- IntegrationCommandAttemptStatusFailedTime as IntegrationCommandAttemptStatusFailedTime,
- IntegrationCommandAttemptStatusPending as IntegrationCommandAttemptStatusPending,
- IntegrationCommandAttemptStatusPendingTime as IntegrationCommandAttemptStatusPendingTime,
- IntegrationCommandAttemptTime as IntegrationCommandAttemptTime,
- IntegrationCommandConnectResponse as IntegrationCommandConnectResponse,
- IntegrationCommandMethod as IntegrationCommandMethod,
- IntegrationCommandStatusResponse as IntegrationCommandStatusResponse,
- IntegrationEnvMethod as IntegrationEnvMethod,
- IntegrationExternalMethod as IntegrationExternalMethod,
- IntegrationGetResponse as IntegrationGetResponse,
- IntegrationID as IntegrationID,
IntegrationInfo as IntegrationInfo,
- IntegrationKeyMethod as IntegrationKeyMethod,
- IntegrationListResponse as IntegrationListResponse,
- IntegrationMethod as IntegrationMethod,
- IntegrationMethodID as IntegrationMethodID,
- IntegrationMethodNotFoundErrorBody as IntegrationMethodNotFoundErrorBody,
- IntegrationNotFoundErrorBody as IntegrationNotFoundErrorBody,
- IntegrationOauthConnectResponse as IntegrationOauthConnectResponse,
- IntegrationOAuthMethod as IntegrationOAuthMethod,
- IntegrationOauthStatusResponse as IntegrationOauthStatusResponse,
- IntegrationUpdatedEvent as IntegrationUpdatedEvent,
- InvalidCursorErrorBody as InvalidCursorErrorBody,
- InvalidRequestErrorBody as InvalidRequestErrorBody,
- Location as Location,
- LocationNotFoundErrorBody as LocationNotFoundErrorBody,
- LocationPublicInfo as LocationPublicInfo,
- LocationPublicInfoProject as LocationPublicInfoProject,
- LocationPublicRef as LocationPublicRef,
- LocationRef as LocationRef,
- LocationShutdownEvent as LocationShutdownEvent,
- McpAddConfig as McpAddConfig,
- McpListResponse as McpListResponse,
- McpLocalConfig as McpLocalConfig,
- McpLocalConfigTimeout as McpLocalConfigTimeout,
- McpOAuthConfig as McpOAuthConfig,
- McpProtocol as McpProtocol,
McpRemoteConfig as McpRemoteConfig,
- McpRemoteConfigTimeout as McpRemoteConfigTimeout,
- McpResource as McpResource,
- McpResourceCatalog as McpResourceCatalog,
- McpResourceCatalogResponse as McpResourceCatalogResponse,
- McpResourcesChangedEvent as McpResourcesChangedEvent,
- McpResourcesChangedEventData as McpResourcesChangedEventData,
- McpResourceTemplate as McpResourceTemplate,
- McpServer as McpServer,
- McpServerNotFoundErrorBody as McpServerNotFoundErrorBody,
- McpServerStatus as McpServerStatus,
- McpStatusChangedEvent as McpStatusChangedEvent,
- McpStatusChangedEventData as McpStatusChangedEventData,
- McpStatusConnected as McpStatusConnected,
- McpStatusDisabled as McpStatusDisabled,
McpStatusFailed as McpStatusFailed,
- McpStatusNeedsAuth as McpStatusNeedsAuth,
+ PermissionReply as PermissionReply,
+ ProjectCommands as ProjectCommands,
+ ProviderRequest as ProviderRequest,
+ PtyCreatedEvent as PtyCreatedEvent,
+ PtyDeletedEvent as PtyDeletedEvent,
+ PtyUpdatedEvent as PtyUpdatedEvent,
+ ReferenceSource as ReferenceSource,
+ SessionMetadata as SessionMetadata,
+ ToolFileContent as ToolFileContent,
+ ToolTextContent as ToolTextContent,
+ WebSearchResult as WebSearchResult,
+ ConnectionStatus as ConnectionStatus,
+ FormBooleanField as FormBooleanField,
+ FormCreatedEvent as FormCreatedEvent,
+ FormIntegerField as FormIntegerField,
+ FormRepliedEvent as FormRepliedEvent,
McpStatusPending as McpStatusPending,
- MessageNotFoundErrorBody as MessageNotFoundErrorBody,
- MigrationV1StatusResponseOption1 as MigrationV1StatusResponseOption1,
- MigrationV1StatusResponseOption2 as MigrationV1StatusResponseOption2,
- MigrationV1StatusResponseOption2Progress as MigrationV1StatusResponseOption2Progress,
- MigrationV1StatusResponseOption3 as MigrationV1StatusResponseOption3,
ModelCacheFormat as ModelCacheFormat,
+ PermissionEffect as PermissionEffect,
+ PermissionSource as PermissionSource,
+ PromptFileSource as PromptFileSource,
+ ProviderEncoding as ProviderEncoding,
+ ProviderSettings as ProviderSettings,
+ SessionIdleEvent as SessionIdleEvent,
+ SessionInboxInfo as SessionInboxInfo,
+ SessionInboxItem as SessionInboxItem,
+ SessionInboxMove as SessionInboxMove,
+ SessionInboxUser as SessionInboxUser,
+ SessionMessageID as SessionMessageID,
+ SessionsResponse as SessionsResponse,
+ SessionStatsInfo as SessionStatsInfo,
+ ShellExitedEvent as ShellExitedEvent,
+ AgentUpdatedEvent as AgentUpdatedEvent,
+ ConfigPluginEntry as ConfigPluginEntry,
+ ConfigShellOption as ConfigShellOption,
+ ConnectionEnvInfo as ConnectionEnvInfo,
+ FormExternalField as FormExternalField,
+ IntegrationMethod as IntegrationMethod,
+ LocationPublicRef as LocationPublicRef,
+ McpStatusDisabled as McpStatusDisabled,
ModelCapabilities as ModelCapabilities,
- ModelCompatibility as ModelCompatibility,
- ModelCost as ModelCost,
- ModelCostCache as ModelCostCache,
- ModelCostTier as ModelCostTier,
- ModelDefaultResponse as ModelDefaultResponse,
- ModelInfo as ModelInfo,
- ModelInfoLimit as ModelInfoLimit,
- ModelInfoTime as ModelInfoTime,
- ModelListResponse as ModelListResponse,
- ModelMaxTokensField as ModelMaxTokensField,
- ModelReasoningField as ModelReasoningField,
- ModelRef as ModelRef,
- ModelsDevRefreshedEvent as ModelsDevRefreshedEvent,
- ModelSettings as ModelSettings,
ModelUpdatedEvent as ModelUpdatedEvent,
- ModelVariant as ModelVariant,
- MoneyUSD as MoneyUSD,
- MoneyUSDPerMillionTokens as MoneyUSDPerMillionTokens,
- PairingCode as PairingCode,
- PairingSession as PairingSession,
- PermissionAskedEvent as PermissionAskedEvent,
- PermissionAskedEventData as PermissionAskedEventData,
- PermissionCreateResponse as PermissionCreateResponse,
- PermissionEffect as PermissionEffect,
- PermissionID as PermissionID,
- PermissionNotFoundErrorBody as PermissionNotFoundErrorBody,
- PermissionRepliedEvent as PermissionRepliedEvent,
- PermissionRepliedEventData as PermissionRepliedEventData,
- PermissionReply as PermissionReply,
PermissionRequest as PermissionRequest,
- PermissionRequestListResponse as PermissionRequestListResponse,
- PermissionRule as PermissionRule,
PermissionRuleset as PermissionRuleset,
PermissionSavedID as PermissionSavedID,
- PermissionSavedInfo as PermissionSavedInfo,
- PermissionSavedInfoTime as PermissionSavedInfoTime,
- PermissionSavedNotFoundErrorBody as PermissionSavedNotFoundErrorBody,
- PermissionSource as PermissionSource,
- PersistentPtyAddedEvent as PersistentPtyAddedEvent,
- PersistentPtyAddedEventData as PersistentPtyAddedEventData,
- PersistentPtyHandoff as PersistentPtyHandoff,
PersistentPtyInfo as PersistentPtyInfo,
- PersistentPtyInfoOutput as PersistentPtyInfoOutput,
- PersistentPtyInfoSize as PersistentPtyInfoSize,
- PersistentPtyReadLines as PersistentPtyReadLines,
- PersistentPtyReadResult as PersistentPtyReadResult,
- PersistentPtyReadResultScreen as PersistentPtyReadResultScreen,
- PersistentPtyReadResultScreenCursor as PersistentPtyReadResultScreenCursor,
- PersistentPtyRemovedEvent as PersistentPtyRemovedEvent,
- PersistentPtyRemovedEventData as PersistentPtyRemovedEventData,
- PersistentPtySnapshot as PersistentPtySnapshot,
- PersistentPtySnapshotCursor as PersistentPtySnapshotCursor,
- PluginCheckResponse as PluginCheckResponse,
- PluginFeatures as PluginFeatures,
- PluginInfo as PluginInfo,
- PluginListResponse as PluginListResponse,
- PluginSource as PluginSource,
- PluginSourceBuiltin as PluginSourceBuiltin,
- PluginSourceLocal as PluginSourceLocal,
- PluginSourcePackage as PluginSourcePackage,
- PluginSourceSdk as PluginSourceSdk,
- PluginState as PluginState,
- PluginStateActive as PluginStateActive,
- PluginStateFailed as PluginStateFailed,
- PluginUpdatedEvent as PluginUpdatedEvent,
- Project as Project,
- ProjectCommands as ProjectCommands,
- ProjectIcon as ProjectIcon,
- ProjectID as ProjectID,
- ProjectNotFoundErrorBody as ProjectNotFoundErrorBody,
- ProjectTime as ProjectTime,
+ ProviderTransport as ProviderTransport,
+ SessionMovedEvent as SessionMovedEvent,
+ SessionStatsTools as SessionStatsTools,
+ ShellCreatedEvent as ShellCreatedEvent,
+ ShellDeletedEvent as ShellDeletedEvent,
+ SkillUpdatedEvent as SkillUpdatedEvent,
+ TuiToastShowEvent as TuiToastShowEvent,
+ WebSearchProvider as WebSearchProvider,
+ WebSearchResponse as WebSearchResponse,
+ WorktreeDirectory as WorktreeDirectory,
+ ConfigReferenceGit as ConfigReferenceGit,
+ ConfigUpdatedEvent as ConfigUpdatedEvent,
+ CredentialExternal as CredentialExternal,
+ FormCancelledEvent as FormCancelledEvent,
+ IntegrationAttempt as IntegrationAttempt,
+ LocationPublicInfo as LocationPublicInfo,
+ McpResourceCatalog as McpResourceCatalog,
+ McpStatusConnected as McpStatusConnected,
+ McpStatusNeedsAuth as McpStatusNeedsAuth,
+ ModelCompatibility as ModelCompatibility,
+ PluginUpdatedEvent as PluginUpdatedEvent,
+ ProviderCompaction as ProviderCompaction,
+ ReferenceGitSource as ReferenceGitSource,
+ SessionForkedEvent as SessionForkedEvent,
+ SessionMessageIdle as SessionMessageIdle,
+ SessionMessageInfo as SessionMessageInfo,
+ SessionMessageUser as SessionMessageUser,
+ SessionViewedEvent as SessionViewedEvent,
+ CommandUpdatedEvent as CommandUpdatedEvent,
+ ConfigModelSettings as ConfigModelSettings,
+ ConfigWebSearchInfo as ConfigWebSearchInfo,
+ InstructionEntryKey as InstructionEntryKey,
+ IntegrationMethodID as IntegrationMethodID,
+ McpResourceTemplate as McpResourceTemplate,
+ ModelMaxTokensField as ModelMaxTokensField,
+ ModelReasoningField as ModelReasoningField,
+ PermissionSavedInfo as PermissionSavedInfo,
ProjectUpdatedEvent as ProjectUpdatedEvent,
- ProjectUpdatedEventData as ProjectUpdatedEventData,
- ProjectVcs as ProjectVcs,
- PromptAgentAttachment as PromptAgentAttachment,
- PromptBase64 as PromptBase64,
+ SessionCreatedEvent as SessionCreatedEvent,
+ SessionDeletedEvent as SessionDeletedEvent,
+ SessionForkBoundary as SessionForkBoundary,
+ SessionMessageShell as SessionMessageShell,
+ SessionMessageSkill as SessionMessageSkill,
+ SessionRenamedEvent as SessionRenamedEvent,
+ SessionTransferData as SessionTransferData,
+ VoiceTransportInput as VoiceTransportInput,
+ ConfigFormatterEntry as ConfigFormatterEntry,
+ ConfigModelReasoning as ConfigModelReasoning,
+ ConfigReferenceLocal as ConfigReferenceLocal,
+ FormMultiselectField as FormMultiselectField,
+ GenerateTextResponse as GenerateTextResponse,
+ InstructionEntryInfo as InstructionEntryInfo,
+ IntegrationAttemptID as IntegrationAttemptID,
+ IntegrationEnvMethod as IntegrationEnvMethod,
+ IntegrationKeyMethod as IntegrationKeyMethod,
+ PermissionAskedEvent as PermissionAskedEvent,
+ PersistentPtyHandoff as PersistentPtyHandoff,
PromptFileAttachment as PromptFileAttachment,
- PromptFileSource as PromptFileSource,
- PromptFileSourceInline as PromptFileSourceInline,
- PromptFileSourceUri as PromptFileSourceUri,
- PromptInputFileAttachment as PromptInputFileAttachment,
- PromptInputSkillAttachment as PromptInputSkillAttachment,
- PromptMention as PromptMention,
- PromptSkillAttachment as PromptSkillAttachment,
- ProviderCompaction as ProviderCompaction,
- ProviderCompactionNative as ProviderCompactionNative,
- ProviderCompactionSummary as ProviderCompactionSummary,
- ProviderCompatibility as ProviderCompatibility,
- ProviderEncoding as ProviderEncoding,
- ProviderGetResponse as ProviderGetResponse,
- ProviderID as ProviderID,
- ProviderInfo as ProviderInfo,
- ProviderListResponse as ProviderListResponse,
- ProviderNotFoundErrorBody as ProviderNotFoundErrorBody,
- ProviderRequest as ProviderRequest,
- ProviderSettings as ProviderSettings,
- ProviderTransport as ProviderTransport,
ProviderUpdatedEvent as ProviderUpdatedEvent,
- Pty as Pty,
- PtyConnectTokenResponse as PtyConnectTokenResponse,
- PtyCreatedEvent as PtyCreatedEvent,
- PtyCreatedEventData as PtyCreatedEventData,
- PtyCreateResponse as PtyCreateResponse,
- PtyDeletedEvent as PtyDeletedEvent,
- PtyDeletedEventData as PtyDeletedEventData,
- PtyExitedEvent as PtyExitedEvent,
- PtyExitedEventData as PtyExitedEventData,
- PtyGetResponse as PtyGetResponse,
- PtyID as PtyID,
- PtyListResponse as PtyListResponse,
- PtyNotFoundErrorBody as PtyNotFoundErrorBody,
- PtyTicketConnectToken as PtyTicketConnectToken,
- PtyUpdatedEvent as PtyUpdatedEvent,
- PtyUpdatedEventData as PtyUpdatedEventData,
- PtyUpdateResponse as PtyUpdateResponse,
- PtyUpdateSize as PtyUpdateSize,
- ReferenceGitSource as ReferenceGitSource,
- ReferenceInfo as ReferenceInfo,
- ReferenceListResponse as ReferenceListResponse,
ReferenceLocalSource as ReferenceLocalSource,
- ReferenceSource as ReferenceSource,
- ReferenceUpdatedEvent as ReferenceUpdatedEvent,
- RelativePath as RelativePath,
- RpcErrorBody as RpcErrorBody,
- RpcEvent as RpcEvent,
- RpcInternalErrorBody as RpcInternalErrorBody,
- RpcOutput as RpcOutput,
ServerConnectedEvent as ServerConnectedEvent,
- ServerInfo as ServerInfo,
- ServerInfoCapabilities as ServerInfoCapabilities,
- ServerInfoPaths as ServerInfoPaths,
- ServiceUnavailableErrorBody as ServiceUnavailableErrorBody,
- SessionActive as SessionActive,
- SessionAgentSelectedEvent as SessionAgentSelectedEvent,
- SessionAgentSelectedEventData as SessionAgentSelectedEventData,
- SessionAgentSelectedEventDurable as SessionAgentSelectedEventDurable,
SessionArchivedEvent as SessionArchivedEvent,
- SessionArchivedEventData as SessionArchivedEventData,
- SessionArchivedEventDurable as SessionArchivedEventDurable,
- SessionBusyErrorBody as SessionBusyErrorBody,
- SessionCompactionDeltaEvent as SessionCompactionDeltaEvent,
- SessionCompactionDeltaEventData as SessionCompactionDeltaEventData,
- SessionCompactionEndedEvent as SessionCompactionEndedEvent,
- SessionCompactionEndedEventData as SessionCompactionEndedEventData,
- SessionCompactionEndedEventDurable as SessionCompactionEndedEventDurable,
- SessionCompactionFailedEvent as SessionCompactionFailedEvent,
- SessionCompactionFailedEventData as SessionCompactionFailedEventData,
- SessionCompactionFailedEventDurable as SessionCompactionFailedEventDurable,
- SessionCompactionStartedEvent as SessionCompactionStartedEvent,
- SessionCompactionStartedEventData as SessionCompactionStartedEventData,
- SessionCompactionStartedEventDurable as SessionCompactionStartedEventDurable,
- SessionCreatedEvent as SessionCreatedEvent,
- SessionCreatedEventData as SessionCreatedEventData,
- SessionCreatedEventDurable as SessionCreatedEventDurable,
- SessionDeletedEvent as SessionDeletedEvent,
- SessionDeletedEventData as SessionDeletedEventData,
- SessionDeletedEventDurable as SessionDeletedEventDurable,
- SessionEventDurable as SessionEventDurable,
- SessionExecutionFailedEvent as SessionExecutionFailedEvent,
- SessionExecutionFailedEventData as SessionExecutionFailedEventData,
- SessionExecutionFailedEventDurable as SessionExecutionFailedEventDurable,
- SessionExecutionInterruptedEvent as SessionExecutionInterruptedEvent,
- SessionExecutionInterruptedEventData as SessionExecutionInterruptedEventData,
- SessionExecutionInterruptedEventDurable as SessionExecutionInterruptedEventDurable,
- SessionExecutionStartedEvent as SessionExecutionStartedEvent,
- SessionExecutionStartedEventData as SessionExecutionStartedEventData,
- SessionExecutionStartedEventDurable as SessionExecutionStartedEventDurable,
- SessionExecutionSucceededEvent as SessionExecutionSucceededEvent,
- SessionExecutionSucceededEventData as SessionExecutionSucceededEventData,
- SessionExecutionSucceededEventDurable as SessionExecutionSucceededEventDurable,
- SessionForkBoundary as SessionForkBoundary,
- SessionForkBoundaryBefore as SessionForkBoundaryBefore,
- SessionForkBoundaryThrough as SessionForkBoundaryThrough,
- SessionForkedEvent as SessionForkedEvent,
- SessionForkedEventData as SessionForkedEventData,
- SessionForkedEventDurable as SessionForkedEventDurable,
- SessionGenerateResponse as SessionGenerateResponse,
- SessionGenerateResponseData as SessionGenerateResponseData,
- SessionID as SessionID,
- SessionIdleEvent as SessionIdleEvent,
- SessionIdleEventData as SessionIdleEventData,
- SessionInboxCancelledEvent as SessionInboxCancelledEvent,
- SessionInboxCancelledEventData as SessionInboxCancelledEventData,
- SessionInboxCancelledEventDurable as SessionInboxCancelledEventDurable,
- SessionInboxCompaction as SessionInboxCompaction,
- SessionInboxCompactionPayload as SessionInboxCompactionPayload,
- SessionInboxCompactionTime as SessionInboxCompactionTime,
- SessionInboxDeliveredEvent as SessionInboxDeliveredEvent,
- SessionInboxDeliveredEventData as SessionInboxDeliveredEventData,
- SessionInboxDeliveredEventDurable as SessionInboxDeliveredEventDurable,
SessionInboxDelivery as SessionInboxDelivery,
- SessionInboxDeliveryChangedEvent as SessionInboxDeliveryChangedEvent,
- SessionInboxDeliveryChangedEventData as SessionInboxDeliveryChangedEventData,
- SessionInboxDeliveryChangedEventDurable as SessionInboxDeliveryChangedEventDurable,
- SessionInboxEnqueuedEvent as SessionInboxEnqueuedEvent,
- SessionInboxEnqueuedEventData as SessionInboxEnqueuedEventData,
- SessionInboxEnqueuedEventDurable as SessionInboxEnqueuedEventDurable,
- SessionInboxInfo as SessionInboxInfo,
- SessionInboxItem as SessionInboxItem,
- SessionInboxItemCompaction as SessionInboxItemCompaction,
- SessionInboxItemMove as SessionInboxItemMove,
- SessionInboxItemSynthetic as SessionInboxItemSynthetic,
- SessionInboxItemUser as SessionInboxItemUser,
- SessionInboxMove as SessionInboxMove,
- SessionInboxMovePayload as SessionInboxMovePayload,
- SessionInboxMovePayload1 as SessionInboxMovePayload1,
- SessionInboxMoveTime as SessionInboxMoveTime,
- SessionInboxSynthetic as SessionInboxSynthetic,
- SessionInboxSyntheticPayload as SessionInboxSyntheticPayload,
- SessionInboxSyntheticTime as SessionInboxSyntheticTime,
- SessionInboxUser as SessionInboxUser,
- SessionInboxUserPayload as SessionInboxUserPayload,
- SessionInboxUserTime as SessionInboxUserTime,
- SessionInfo as SessionInfo,
- SessionInfoFork as SessionInfoFork,
- SessionInfoTime as SessionInfoTime,
- SessionInstructionsUpdatedEvent as SessionInstructionsUpdatedEvent,
- SessionInstructionsUpdatedEventData as SessionInstructionsUpdatedEventData,
- SessionInstructionsUpdatedEventDurable as SessionInstructionsUpdatedEventDurable,
- SessionInterruptResponse as SessionInterruptResponse,
- SessionLogItem as SessionLogItem,
- SessionMessageAgentSelected as SessionMessageAgentSelected,
- SessionMessageAgentSelectedTime as SessionMessageAgentSelectedTime,
- SessionMessageAssistant as SessionMessageAssistant,
- SessionMessageAssistantContent as SessionMessageAssistantContent,
- SessionMessageAssistantContentItem as SessionMessageAssistantContentItem,
- SessionMessageAssistantReasoning as SessionMessageAssistantReasoning,
- SessionMessageAssistantReasoningTime as SessionMessageAssistantReasoningTime,
- SessionMessageAssistantRetry as SessionMessageAssistantRetry,
- SessionMessageAssistantSnapshot as SessionMessageAssistantSnapshot,
- SessionMessageAssistantText as SessionMessageAssistantText,
- SessionMessageAssistantTime as SessionMessageAssistantTime,
- SessionMessageAssistantTool as SessionMessageAssistantTool,
- SessionMessageAssistantToolState as SessionMessageAssistantToolState,
- SessionMessageAssistantToolTime as SessionMessageAssistantToolTime,
- SessionMessageCompaction as SessionMessageCompaction,
- SessionMessageCompactionCompleted as SessionMessageCompactionCompleted,
- SessionMessageCompactionCompletedTime as SessionMessageCompactionCompletedTime,
- SessionMessageCompactionFailed as SessionMessageCompactionFailed,
- SessionMessageCompactionFailedTime as SessionMessageCompactionFailedTime,
- SessionMessageCompactionRunning as SessionMessageCompactionRunning,
- SessionMessageCompactionRunningTime as SessionMessageCompactionRunningTime,
- SessionMessageContentUpdatedEvent as SessionMessageContentUpdatedEvent,
- SessionMessageContentUpdatedEventData as SessionMessageContentUpdatedEventData,
- SessionMessageContentUpdatedEventDurable as SessionMessageContentUpdatedEventDurable,
- SessionMessageID as SessionMessageID,
- SessionMessageIdle as SessionMessageIdle,
- SessionMessageIdleTime as SessionMessageIdleTime,
- SessionMessageInfo as SessionMessageInfo,
- SessionMessageLocationSwitched as SessionMessageLocationSwitched,
- SessionMessageLocationSwitchedPrevious as SessionMessageLocationSwitchedPrevious,
- SessionMessageLocationSwitchedTime as SessionMessageLocationSwitchedTime,
- SessionMessageModelSelected as SessionMessageModelSelected,
- SessionMessageModelSelectedTime as SessionMessageModelSelectedTime,
SessionMessageNative as SessionMessageNative,
- SessionMessageShell as SessionMessageShell,
- SessionMessageShellOutput as SessionMessageShellOutput,
- SessionMessageShellTime as SessionMessageShellTime,
- SessionMessageSkill as SessionMessageSkill,
- SessionMessageSkillTime as SessionMessageSkillTime,
- SessionMessagesResponse as SessionMessagesResponse,
- SessionMessagesResponseCursor as SessionMessagesResponseCursor,
- SessionMessageSynthetic as SessionMessageSynthetic,
- SessionMessageSyntheticTime as SessionMessageSyntheticTime,
SessionMessageSystem as SessionMessageSystem,
- SessionMessageSystemTime as SessionMessageSystemTime,
- SessionMessageToolStateCompleted as SessionMessageToolStateCompleted,
- SessionMessageToolStateError as SessionMessageToolStateError,
- SessionMessageToolStateRunning as SessionMessageToolStateRunning,
- SessionMessageToolStateStreaming as SessionMessageToolStateStreaming,
- SessionMessageUser as SessionMessageUser,
- SessionMessageUserTime as SessionMessageUserTime,
- SessionMetadata as SessionMetadata,
- SessionMetadataUpdatedEvent as SessionMetadataUpdatedEvent,
- SessionMetadataUpdatedEventData as SessionMetadataUpdatedEventData,
- SessionMetadataUpdatedEventDurable as SessionMetadataUpdatedEventDurable,
- SessionModelSelectedEvent as SessionModelSelectedEvent,
- SessionModelSelectedEventData as SessionModelSelectedEventData,
- SessionModelSelectedEventDurable as SessionModelSelectedEventDurable,
- SessionMovedEvent as SessionMovedEvent,
- SessionMovedEventData as SessionMovedEventData,
- SessionMovedEventDurable as SessionMovedEventDurable,
- SessionNotFoundErrorBody as SessionNotFoundErrorBody,
- SessionPermissionsEvent as SessionPermissionsEvent,
- SessionPermissionsEventData as SessionPermissionsEventData,
- SessionPermissionsEventDurable as SessionPermissionsEventDurable,
- SessionReasoningDeltaEvent as SessionReasoningDeltaEvent,
- SessionReasoningDeltaEventData as SessionReasoningDeltaEventData,
- SessionReasoningEndedEvent as SessionReasoningEndedEvent,
- SessionReasoningEndedEventData as SessionReasoningEndedEventData,
- SessionReasoningEndedEventDurable as SessionReasoningEndedEventDurable,
- SessionReasoningStartedEvent as SessionReasoningStartedEvent,
- SessionReasoningStartedEventData as SessionReasoningStartedEventData,
- SessionReasoningStartedEventDurable as SessionReasoningStartedEventDurable,
- SessionRenamedEvent as SessionRenamedEvent,
- SessionRenamedEventData as SessionRenamedEventData,
- SessionRenamedEventDurable as SessionRenamedEventDurable,
- SessionRetryScheduledEvent as SessionRetryScheduledEvent,
- SessionRetryScheduledEventData as SessionRetryScheduledEventData,
- SessionRetryScheduledEventDurable as SessionRetryScheduledEventDurable,
- SessionRevert as SessionRevert,
- SessionRevertClearedEvent as SessionRevertClearedEvent,
- SessionRevertClearedEventData as SessionRevertClearedEventData,
- SessionRevertClearedEventDurable as SessionRevertClearedEventDurable,
- SessionRevertCommittedEvent as SessionRevertCommittedEvent,
- SessionRevertCommittedEventData as SessionRevertCommittedEventData,
- SessionRevertCommittedEventDurable as SessionRevertCommittedEventDurable,
- SessionRevertStagedEvent as SessionRevertStagedEvent,
- SessionRevertStagedEventData as SessionRevertStagedEventData,
- SessionRevertStagedEventDurable as SessionRevertStagedEventDurable,
- SessionsCursor as SessionsCursor,
- SessionShellEndedEvent as SessionShellEndedEvent,
- SessionShellEndedEventData as SessionShellEndedEventData,
- SessionShellEndedEventDataOutput as SessionShellEndedEventDataOutput,
- SessionShellEndedEventDurable as SessionShellEndedEventDurable,
- SessionShellStartedEvent as SessionShellStartedEvent,
- SessionShellStartedEventData as SessionShellStartedEventData,
- SessionShellStartedEventDurable as SessionShellStartedEventDurable,
- SessionSkillActivatedEvent as SessionSkillActivatedEvent,
- SessionSkillActivatedEventData as SessionSkillActivatedEventData,
- SessionSkillActivatedEventDurable as SessionSkillActivatedEventDurable,
- SessionsResponse as SessionsResponse,
- SessionsResponseCursor as SessionsResponseCursor,
SessionStatsActivity as SessionStatsActivity,
- SessionStatsInfo as SessionStatsInfo,
- SessionStatsInfoRange as SessionStatsInfoRange,
- SessionStatsModelUsage as SessionStatsModelUsage,
- SessionStatsTools as SessionStatsTools,
- SessionStatsToolsDetail as SessionStatsToolsDetail,
- SessionStatsToolsNone as SessionStatsToolsNone,
- SessionStatsToolsSummary as SessionStatsToolsSummary,
- SessionStatsToolTotals as SessionStatsToolTotals,
- SessionStatsToolUsage as SessionStatsToolUsage,
- SessionStatus as SessionStatus,
- SessionStatusBusy as SessionStatusBusy,
- SessionStatusIdle as SessionStatusIdle,
- SessionStatusRetry as SessionStatusRetry,
- SessionStatusRetryAction as SessionStatusRetryAction,
SessionStatusUpdated as SessionStatusUpdated,
- SessionStatusUpdatedData as SessionStatusUpdatedData,
+ TuiPromptAppendEvent as TuiPromptAppendEvent,
+ VoiceSessionResponse as VoiceSessionResponse,
+ VoiceWebRTCTransport as VoiceWebRTCTransport,
+ WorktreeUpdatedEvent as WorktreeUpdatedEvent,
+ LocationShutdownEvent as LocationShutdownEvent,
+ McpStatusChangedEvent as McpStatusChangedEvent,
+ PersistentPtySnapshot as PersistentPtySnapshot,
+ PromptAgentAttachment as PromptAgentAttachment,
+ PromptSkillAttachment as PromptSkillAttachment,
+ ProviderCompatibility as ProviderCompatibility,
+ PtyTicketConnectToken as PtyTicketConnectToken,
+ ReferenceUpdatedEvent as ReferenceUpdatedEvent,
+ SessionInboxSynthetic as SessionInboxSynthetic,
+ SessionMessageNative1 as SessionMessageNative1,
+ SessionMessageNative2 as SessionMessageNative2,
+ SessionMessageNative3 as SessionMessageNative3,
+ SessionMessageNative4 as SessionMessageNative4,
+ SessionMessageNative5 as SessionMessageNative5,
+ SessionMessageNative6 as SessionMessageNative6,
+ SessionMessageNative7 as SessionMessageNative7,
+ SessionMessageNative8 as SessionMessageNative8,
+ SessionMessageNative9 as SessionMessageNative9,
+ SessionStatsToolUsage as SessionStatsToolUsage,
SessionStepEndedEvent as SessionStepEndedEvent,
- SessionStepEndedEventData as SessionStepEndedEventData,
- SessionStepEndedEventDurable as SessionStepEndedEventDurable,
- SessionStepFailedEvent as SessionStepFailedEvent,
- SessionStepFailedEventData as SessionStepFailedEventData,
- SessionStepFailedEventDurable as SessionStepFailedEventDurable,
- SessionStepStartedEvent as SessionStepStartedEvent,
- SessionStepStartedEventData as SessionStepStartedEventData,
- SessionStepStartedEventDurable as SessionStepStartedEventDurable,
- SessionStepStreamedEvent as SessionStepStreamedEvent,
- SessionStepStreamedEventData as SessionStepStreamedEventData,
- SessionStepStreamedEventDurable as SessionStepStreamedEventDurable,
- SessionStructuredError as SessionStructuredError,
- SessionStructuredErrorResponse as SessionStructuredErrorResponse,
SessionSyntheticEvent as SessionSyntheticEvent,
- SessionSyntheticEventData as SessionSyntheticEventData,
- SessionSyntheticEventDurable as SessionSyntheticEventDurable,
SessionTextDeltaEvent as SessionTextDeltaEvent,
- SessionTextDeltaEventData as SessionTextDeltaEventData,
SessionTextEndedEvent as SessionTextEndedEvent,
- SessionTextEndedEventData as SessionTextEndedEventData,
- SessionTextEndedEventDurable as SessionTextEndedEventDurable,
- SessionTextStartedEvent as SessionTextStartedEvent,
- SessionTextStartedEventData as SessionTextStartedEventData,
- SessionTextStartedEventDurable as SessionTextStartedEventDurable,
+ TuiSessionSelectEvent as TuiSessionSelectEvent,
+ VcsBranchUpdatedEvent as VcsBranchUpdatedEvent,
+ WebsearchUpdatedEvent as WebsearchUpdatedEvent,
+ WorktreeResolvedEvent as WorktreeResolvedEvent,
+ ConfigProviderSettings as ConfigProviderSettings,
+ CredentialUpdatedEvent as CredentialUpdatedEvent,
+ FilesystemChangedEvent as FilesystemChangedEvent,
+ IntegrationOAuthMethod as IntegrationOAuthMethod,
+ PermissionRepliedEvent as PermissionRepliedEvent,
+ PersistentPtyReadLines as PersistentPtyReadLines,
+ SessionInboxCompaction as SessionInboxCompaction,
+ SessionMessageNative10 as SessionMessageNative10,
+ SessionMessageNative11 as SessionMessageNative11,
+ SessionMessageNative12 as SessionMessageNative12,
+ SessionShellEndedEvent as SessionShellEndedEvent,
+ SessionStatsModelUsage as SessionStatsModelUsage,
+ SessionStatsToolTotals as SessionStatsToolTotals,
+ SessionStepFailedEvent as SessionStepFailedEvent,
+ SessionStructuredError as SessionStructuredError,
SessionToolCalledEvent as SessionToolCalledEvent,
- SessionToolCalledEventData as SessionToolCalledEventData,
- SessionToolCalledEventDurable as SessionToolCalledEventDurable,
SessionToolFailedEvent as SessionToolFailedEvent,
- SessionToolFailedEventData as SessionToolFailedEventData,
- SessionToolFailedEventDurable as SessionToolFailedEventDurable,
- SessionToolInputDeltaEvent as SessionToolInputDeltaEvent,
- SessionToolInputDeltaEventData as SessionToolInputDeltaEventData,
- SessionToolInputEndedEvent as SessionToolInputEndedEvent,
- SessionToolInputEndedEventData as SessionToolInputEndedEventData,
- SessionToolInputEndedEventDurable as SessionToolInputEndedEventDurable,
- SessionToolInputStartedEvent as SessionToolInputStartedEvent,
- SessionToolInputStartedEventData as SessionToolInputStartedEventData,
- SessionToolInputStartedEventDurable as SessionToolInputStartedEventDurable,
- SessionToolProgressEvent as SessionToolProgressEvent,
- SessionToolProgressEventData as SessionToolProgressEventData,
- SessionToolSuccessEvent as SessionToolSuccessEvent,
- SessionToolSuccessEventData as SessionToolSuccessEventData,
- SessionToolSuccessEventDurable as SessionToolSuccessEventDurable,
- SessionTransferData as SessionTransferData,
SessionUnarchivedEvent as SessionUnarchivedEvent,
- SessionUnarchivedEventData as SessionUnarchivedEventData,
- SessionUnarchivedEventDurable as SessionUnarchivedEventDurable,
- SessionUsageRecordedEvent as SessionUsageRecordedEvent,
- SessionUsageRecordedEventData as SessionUsageRecordedEventData,
- SessionUsageRecordedEventDurable as SessionUsageRecordedEventDurable,
- SessionUsageUpdatedEvent as SessionUsageUpdatedEvent,
- SessionUsageUpdatedEventData as SessionUsageUpdatedEventData,
- SessionViewedEvent as SessionViewedEvent,
- SessionViewedEventData as SessionViewedEventData,
- SessionViewedEventDurable as SessionViewedEventDurable,
- ShellCreatedEvent as ShellCreatedEvent,
- ShellCreatedEventData as ShellCreatedEventData,
- ShellCreateResponse as ShellCreateResponse,
- ShellDeletedEvent as ShellDeletedEvent,
- ShellDeletedEventData as ShellDeletedEventData,
- ShellExitedEvent as ShellExitedEvent,
- ShellExitedEventData as ShellExitedEventData,
- ShellGetResponse as ShellGetResponse,
- ShellID as ShellID,
- ShellInfo as ShellInfo,
- ShellInfoTime as ShellInfoTime,
- ShellListResponse as ShellListResponse,
- ShellNotFoundErrorBody as ShellNotFoundErrorBody,
- ShellOutputResponse as ShellOutputResponse,
- ShellOutputResponseData as ShellOutputResponseData,
- SkillID as SkillID,
- SkillInfo as SkillInfo,
- SkillListResponse as SkillListResponse,
- SkillName as SkillName,
- SkillNotFoundErrorBody as SkillNotFoundErrorBody,
- SkillUpdatedEvent as SkillUpdatedEvent,
- SnapshotID as SnapshotID,
- TokenUsageInfo as TokenUsageInfo,
- TokenUsageInfoCache as TokenUsageInfoCache,
- ToolContent as ToolContent,
- ToolFileContent as ToolFileContent,
- ToolTextContent as ToolTextContent,
TuiCommandExecuteEvent as TuiCommandExecuteEvent,
- TuiCommandExecuteEventData as TuiCommandExecuteEventData,
- TuiPromptAppendEvent as TuiPromptAppendEvent,
- TuiPromptAppendEventData as TuiPromptAppendEventData,
- TuiSessionSelectEvent as TuiSessionSelectEvent,
- TuiSessionSelectEventData as TuiSessionSelectEventData,
- TuiToastShowEvent as TuiToastShowEvent,
- TuiToastShowEventData as TuiToastShowEventData,
- UnauthorizedErrorBody as UnauthorizedErrorBody,
- UnknownConfigEntry as UnknownConfigEntry,
- UnknownConfigInfoMcpServersValue as UnknownConfigInfoMcpServersValue,
- UnknownConfigModelReasoning as UnknownConfigModelReasoning,
- UnknownConnectionInfo as UnknownConnectionInfo,
- UnknownCredentialValue as UnknownCredentialValue,
- UnknownErrorBody as UnknownErrorBody,
- UnknownEvent as UnknownEvent,
- UnknownFormField as UnknownFormField,
- UnknownFormState as UnknownFormState,
- UnknownIntegrationAttemptStatus as UnknownIntegrationAttemptStatus,
- UnknownIntegrationCommandAttemptStatus as UnknownIntegrationCommandAttemptStatus,
- UnknownIntegrationMethod as UnknownIntegrationMethod,
- UnknownMcpAddConfig as UnknownMcpAddConfig,
- UnknownMcpServerStatus as UnknownMcpServerStatus,
- UnknownPluginSource as UnknownPluginSource,
- UnknownPluginState as UnknownPluginState,
- UnknownPromptFileSource as UnknownPromptFileSource,
- UnknownProviderCompaction as UnknownProviderCompaction,
- UnknownReferenceSource as UnknownReferenceSource,
- UnknownSessionEventDurable as UnknownSessionEventDurable,
- UnknownSessionForkBoundary as UnknownSessionForkBoundary,
- UnknownSessionInboxInfo as UnknownSessionInboxInfo,
- UnknownSessionInboxItem as UnknownSessionInboxItem,
- UnknownSessionMessageAssistantContent as UnknownSessionMessageAssistantContent,
- UnknownSessionMessageAssistantContentItem as UnknownSessionMessageAssistantContentItem,
- UnknownSessionMessageAssistantToolState as UnknownSessionMessageAssistantToolState,
- UnknownSessionMessageCompaction as UnknownSessionMessageCompaction,
- UnknownSessionMessageInfo as UnknownSessionMessageInfo,
- UnknownSessionStatsTools as UnknownSessionStatsTools,
- UnknownSessionStatus as UnknownSessionStatus,
- UnknownToolContent as UnknownToolContent,
- UnknownVoiceTransport as UnknownVoiceTransport,
- UnknownVoiceTransportInput as UnknownVoiceTransportInput,
- VcsBase as VcsBase,
- VcsBaseResponse as VcsBaseResponse,
- VcsBranch as VcsBranch,
- VcsBranchList as VcsBranchList,
- VcsBranchListResponse as VcsBranchListResponse,
- VcsBranchUpdatedEvent as VcsBranchUpdatedEvent,
- VcsBranchUpdatedEventData as VcsBranchUpdatedEventData,
- VcsDiffResponse as VcsDiffResponse,
- VcsFileStatus as VcsFileStatus,
- VcsGetResponse as VcsGetResponse,
- VcsInfo as VcsInfo,
- VcsInitNotSupportedErrorBody as VcsInitNotSupportedErrorBody,
- VcsMode as VcsMode,
- VcsStatusResponse as VcsStatusResponse,
- VoiceSessionResponse as VoiceSessionResponse,
- VoiceSessionResponseData as VoiceSessionResponseData,
- VoiceTransport as VoiceTransport,
- VoiceTransportInput as VoiceTransportInput,
- VoiceWebRTCTransport as VoiceWebRTCTransport,
- VoiceWebRTCTransportInput as VoiceWebRTCTransportInput,
+ ConfigModelCapabilities as ConfigModelCapabilities,
+ CredentialSwitchedEvent as CredentialSwitchedEvent,
+ IntegrationUpdatedEvent as IntegrationUpdatedEvent,
+ ModelsDevRefreshedEvent as ModelsDevRefreshedEvent,
+ PersistentPtyAddedEvent as PersistentPtyAddedEvent,
+ PersistentPtyReadResult as PersistentPtyReadResult,
+ SessionGenerateResponse as SessionGenerateResponse,
+ SessionInboxMovePayload as SessionInboxMovePayload,
+ SessionInboxUserPayload as SessionInboxUserPayload,
+ SessionMessageAssistant as SessionMessageAssistant,
+ SessionMessagesResponse as SessionMessagesResponse,
+ SessionMessageSynthetic as SessionMessageSynthetic,
+ SessionPermissionsEvent as SessionPermissionsEvent,
+ SessionStepStartedEvent as SessionStepStartedEvent,
+ SessionTextStartedEvent as SessionTextStartedEvent,
+ SessionToolSuccessEvent as SessionToolSuccessEvent,
VoiceWebSocketTransport as VoiceWebSocketTransport,
+ ConnectionCredentialInfo as ConnectionCredentialInfo,
+ InstallationUpdatedEvent as InstallationUpdatedEvent,
+ InstructionEntrySnapshot as InstructionEntrySnapshot,
+ IntegrationAttemptStatus as IntegrationAttemptStatus,
+ IntegrationCommandMethod as IntegrationCommandMethod,
+ McpResourcesChangedEvent as McpResourcesChangedEvent,
+ MoneyUSDPerMillionTokens as MoneyUSDPerMillionTokens,
+ SessionInboxMovePayload1 as SessionInboxMovePayload1,
+ SessionInterruptResponse as SessionInterruptResponse,
+ SessionMessageCompaction as SessionMessageCompaction,
+ SessionRevertStagedEvent as SessionRevertStagedEvent,
+ SessionShellStartedEvent as SessionShellStartedEvent,
+ SessionStepStreamedEvent as SessionStepStreamedEvent,
+ SessionToolProgressEvent as SessionToolProgressEvent,
+ SessionUsageUpdatedEvent as SessionUsageUpdatedEvent,
+ IntegrationCommandAttempt as IntegrationCommandAttempt,
+ IntegrationExternalMethod as IntegrationExternalMethod,
+ PersistentPtyRemovedEvent as PersistentPtyRemovedEvent,
+ PromptInputFileAttachment as PromptInputFileAttachment,
+ SessionAgentSelectedEvent as SessionAgentSelectedEvent,
+ SessionInboxEnqueuedEvent as SessionInboxEnqueuedEvent,
+ SessionModelSelectedEvent as SessionModelSelectedEvent,
+ SessionRevertClearedEvent as SessionRevertClearedEvent,
+ VoiceWebRTCTransportInput as VoiceWebRTCTransportInput,
+ PromptInputSkillAttachment as PromptInputSkillAttachment,
+ SessionInboxCancelledEvent as SessionInboxCancelledEvent,
+ SessionInboxDeliveredEvent as SessionInboxDeliveredEvent,
+ SessionReasoningDeltaEvent as SessionReasoningDeltaEvent,
+ SessionReasoningEndedEvent as SessionReasoningEndedEvent,
+ SessionRetryScheduledEvent as SessionRetryScheduledEvent,
+ SessionSkillActivatedEvent as SessionSkillActivatedEvent,
+ SessionToolInputDeltaEvent as SessionToolInputDeltaEvent,
+ SessionToolInputEndedEvent as SessionToolInputEndedEvent,
+ SessionCompactionDeltaEvent as SessionCompactionDeltaEvent,
+ SessionCompactionEndedEvent as SessionCompactionEndedEvent,
+ SessionExecutionFailedEvent as SessionExecutionFailedEvent,
+ SessionMessageAgentSelected as SessionMessageAgentSelected,
+ SessionMessageAssistantText as SessionMessageAssistantText,
+ SessionMessageAssistantTool as SessionMessageAssistantTool,
+ SessionMessageModelSelected as SessionMessageModelSelected,
+ SessionMetadataUpdatedEvent as SessionMetadataUpdatedEvent,
+ SessionRevertCommittedEvent as SessionRevertCommittedEvent,
+ SessionCompactionFailedEvent as SessionCompactionFailedEvent,
+ SessionExecutionStartedEvent as SessionExecutionStartedEvent,
+ SessionInboxSyntheticPayload as SessionInboxSyntheticPayload,
+ SessionMessageAssistantRetry as SessionMessageAssistantRetry,
+ SessionMessageToolStateError as SessionMessageToolStateError,
+ SessionReasoningStartedEvent as SessionReasoningStartedEvent,
+ SessionToolInputStartedEvent as SessionToolInputStartedEvent,
VoiceWebSocketTransportInput as VoiceWebSocketTransportInput,
- WebSearchID as WebSearchID,
- WebSearchProvider as WebSearchProvider,
- WebsearchProvidersResponse as WebsearchProvidersResponse,
- WebsearchQueryResponse as WebsearchQueryResponse,
- WebSearchResponse as WebSearchResponse,
- WebSearchResult as WebSearchResult,
- WebSearchResultTime as WebSearchResultTime,
- WebsearchUpdatedEvent as WebsearchUpdatedEvent,
- WorkspaceID as WorkspaceID,
- WorktreeDirectory as WorktreeDirectory,
- WorktreeErrorBody as WorktreeErrorBody,
- WorktreeErrorBodyData as WorktreeErrorBodyData,
- WorktreeInfo as WorktreeInfo,
- WorktreeList as WorktreeList,
- WorktreeResolvedEvent as WorktreeResolvedEvent,
- WorktreeResolvedEventData as WorktreeResolvedEventData,
- WorktreeResolvedEventDurable as WorktreeResolvedEventDurable,
- WorktreeUpdatedEvent as WorktreeUpdatedEvent,
- WorktreeUpdatedEventData as WorktreeUpdatedEventData,
+ SessionCompactionStartedEvent as SessionCompactionStartedEvent,
+ SessionInboxCompactionPayload as SessionInboxCompactionPayload,
+ SessionExecutionSucceededEvent as SessionExecutionSucceededEvent,
+ SessionMessageCompactionFailed as SessionMessageCompactionFailed,
+ SessionMessageLocationSwitched as SessionMessageLocationSwitched,
+ SessionMessageToolStateRunning as SessionMessageToolStateRunning,
+ IntegrationCommandAttemptStatus as IntegrationCommandAttemptStatus,
+ SessionInstructionsUpdatedEvent as SessionInstructionsUpdatedEvent,
+ SessionMessageCompactionRunning as SessionMessageCompactionRunning,
+ InstallationUpdateAvailableEvent as InstallationUpdateAvailableEvent,
+ SessionExecutionInterruptedEvent as SessionExecutionInterruptedEvent,
+ SessionInboxDeliveryChangedEvent as SessionInboxDeliveryChangedEvent,
+ SessionMessageAssistantReasoning as SessionMessageAssistantReasoning,
+ SessionMessageToolStateCompleted as SessionMessageToolStateCompleted,
+ SessionMessageToolStateStreaming as SessionMessageToolStateStreaming,
+ SessionMessageCompactionCompleted as SessionMessageCompactionCompleted,
)
-
-__all__ = [
- "AbsolutePath",
- "AgentColor",
- "AgentGetResponse",
- "AgentID",
- "AgentInfo",
- "AgentListResponse",
- "AgentNotFoundErrorBody",
- "AgentUpdatedEvent",
- "CommandExecutionErrorBody",
- "CommandInfo",
- "CommandListResponse",
- "CommandNotFoundErrorBody",
- "CommandUpdatedEvent",
- "ConfigAgent",
- "ConfigAgentModel",
- "ConfigAgentRequest",
- "ConfigCommand",
- "ConfigCommandModel",
- "ConfigDirectory",
- "ConfigDocument",
- "ConfigEntry",
- "ConfigFormatterEntry",
- "ConfigInfo",
- "ConfigInfoCompaction",
- "ConfigInfoCompactionKeep",
- "ConfigInfoEnterprise",
- "ConfigInfoExperimental",
- "ConfigInfoExperimentalPoliciesItem",
- "ConfigInfoLspOption2Value",
- "ConfigInfoMcp",
- "ConfigInfoMcpServersValue",
- "ConfigInfoMcpTimeout",
- "ConfigInfoMedia",
- "ConfigInfoMediaImage",
- "ConfigInfoModel",
- "ConfigInfoPoliciesItem",
- "ConfigInfoToolOutput",
- "ConfigInfoWatcher",
- "ConfigLSPServer",
- "ConfigModel",
- "ConfigModelCapabilities",
- "ConfigModelCost",
- "ConfigModelCostCache",
- "ConfigModelCostTier",
- "ConfigModelLimit",
- "ConfigModelReasoning",
- "ConfigModelReasoningFalse",
- "ConfigModelReasoningTrue",
- "ConfigModelReasoningTrueBudget",
- "ConfigModelSettings",
- "ConfigModelVariantsItem",
- "ConfigPluginEntry",
- "ConfigProvider",
- "ConfigProviderSettings",
- "ConfigReferenceGit",
- "ConfigReferenceLocal",
- "ConfigShellOption",
- "ConfigUpdatedEvent",
- "ConfigWarming",
- "ConfigWebSearchInfo",
- "ConfigWorktree",
- "ConflictErrorBody",
- "ConnectionCredentialInfo",
- "ConnectionEnvInfo",
- "ConnectionInfo",
- "ConnectionStatus",
- "CredentialEntry",
- "CredentialExternal",
- "CredentialID",
- "CredentialKey",
- "CredentialOAuth",
- "CredentialSwitchedEvent",
- "CredentialSwitchedEventData",
- "CredentialUpdatedEvent",
- "CredentialValue",
- "Event",
- "EventID",
- "EventLogSynced",
- "EventSeq",
- "EventVersion",
- "EventVersion10",
- "ExperimentalPersistentPtyCreateSize",
- "ExperimentalPersistentPtyHandoffResponse",
- "ExperimentalPersistentPtyUpdateSize",
- "FileDiffInfo",
- "FileFindResponse",
- "FileListResponse",
- "FileNotFoundErrorBody",
- "FileSystemEntry",
- "FileSystemWrite",
- "FileWriteResponse",
- "FilesystemChangedEvent",
- "FilesystemChangedEventData",
- "ForbiddenErrorBody",
- "FormAlreadySettledErrorBody",
- "FormAnswer",
- "FormBooleanField",
- "FormCancelledEvent",
- "FormCancelledEventData",
- "FormCreatedEvent",
- "FormCreatedEventData",
- "FormDetail",
- "FormExternalField",
- "FormField",
- "FormFields",
- "FormID",
- "FormInfo",
- "FormIntegerField",
- "FormInvalidAnswerErrorBody",
- "FormListResponse",
- "FormMetadata",
- "FormMultiselectField",
- "FormNotFoundErrorBody",
- "FormNumberField",
- "FormOption",
- "FormRepliedEvent",
- "FormRepliedEventData",
- "FormState",
- "FormStateAnswered",
- "FormStateCancelled",
- "FormStatePending",
- "FormStringField",
- "FormValue",
- "FormWhen",
- "GenerateTextResponse",
- "GenerateTextResponseData",
- "InstallationUpdateAvailableEvent",
- "InstallationUpdateAvailableEventData",
- "InstallationUpdatedEvent",
- "InstallationUpdatedEventData",
- "InstructionEntryInfo",
- "InstructionEntryKey",
- "InstructionEntrySnapshot",
- "InstructionEntrySnapshotItem",
- "InstructionEntryValueTooLargeErrorBody",
- "InstructionHash",
- "InstructionKey",
- "IntegrationAttempt",
- "IntegrationAttemptID",
- "IntegrationAttemptNotFoundErrorBody",
- "IntegrationAttemptStatus",
- "IntegrationAttemptStatusComplete",
- "IntegrationAttemptStatusCompleteTime",
- "IntegrationAttemptStatusExpired",
- "IntegrationAttemptStatusExpiredTime",
- "IntegrationAttemptStatusFailed",
- "IntegrationAttemptStatusFailedTime",
- "IntegrationAttemptStatusPending",
- "IntegrationAttemptStatusPendingTime",
- "IntegrationAttemptTime",
- "IntegrationCommandAttempt",
- "IntegrationCommandAttemptStatus",
- "IntegrationCommandAttemptStatusComplete",
- "IntegrationCommandAttemptStatusCompleteTime",
- "IntegrationCommandAttemptStatusExpired",
- "IntegrationCommandAttemptStatusExpiredTime",
- "IntegrationCommandAttemptStatusFailed",
- "IntegrationCommandAttemptStatusFailedTime",
- "IntegrationCommandAttemptStatusPending",
- "IntegrationCommandAttemptStatusPendingTime",
- "IntegrationCommandAttemptTime",
- "IntegrationCommandConnectResponse",
- "IntegrationCommandMethod",
- "IntegrationCommandStatusResponse",
- "IntegrationEnvMethod",
- "IntegrationExternalMethod",
- "IntegrationGetResponse",
- "IntegrationID",
- "IntegrationInfo",
- "IntegrationKeyMethod",
- "IntegrationListResponse",
- "IntegrationMethod",
- "IntegrationMethodID",
- "IntegrationMethodNotFoundErrorBody",
- "IntegrationNotFoundErrorBody",
- "IntegrationOAuthMethod",
- "IntegrationOauthConnectResponse",
- "IntegrationOauthStatusResponse",
- "IntegrationUpdatedEvent",
- "InvalidCursorErrorBody",
- "InvalidRequestErrorBody",
- "Location",
- "LocationNotFoundErrorBody",
- "LocationPublicInfo",
- "LocationPublicInfoProject",
- "LocationPublicRef",
- "LocationRef",
- "LocationShutdownEvent",
- "McpAddConfig",
- "McpListResponse",
- "McpLocalConfig",
- "McpLocalConfigTimeout",
- "McpOAuthConfig",
- "McpProtocol",
- "McpRemoteConfig",
- "McpRemoteConfigTimeout",
- "McpResource",
- "McpResourceCatalog",
- "McpResourceCatalogResponse",
- "McpResourceTemplate",
- "McpResourcesChangedEvent",
- "McpResourcesChangedEventData",
- "McpServer",
- "McpServerNotFoundErrorBody",
- "McpServerStatus",
- "McpStatusChangedEvent",
- "McpStatusChangedEventData",
- "McpStatusConnected",
- "McpStatusDisabled",
- "McpStatusFailed",
- "McpStatusNeedsAuth",
- "McpStatusPending",
- "MessageNotFoundErrorBody",
- "MigrationV1StatusResponseOption1",
- "MigrationV1StatusResponseOption2",
- "MigrationV1StatusResponseOption2Progress",
- "MigrationV1StatusResponseOption3",
- "ModelCacheFormat",
- "ModelCapabilities",
- "ModelCompatibility",
- "ModelCost",
- "ModelCostCache",
- "ModelCostTier",
- "ModelDefaultResponse",
- "ModelInfo",
- "ModelInfoLimit",
- "ModelInfoTime",
- "ModelListResponse",
- "ModelMaxTokensField",
- "ModelReasoningField",
- "ModelRef",
- "ModelSettings",
- "ModelUpdatedEvent",
- "ModelVariant",
- "ModelsDevRefreshedEvent",
- "MoneyUSD",
- "MoneyUSDPerMillionTokens",
- "PairingCode",
- "PairingSession",
- "PermissionAskedEvent",
- "PermissionAskedEventData",
- "PermissionCreateResponse",
- "PermissionEffect",
- "PermissionID",
- "PermissionNotFoundErrorBody",
- "PermissionRepliedEvent",
- "PermissionRepliedEventData",
- "PermissionReply",
- "PermissionRequest",
- "PermissionRequestListResponse",
- "PermissionRule",
- "PermissionRuleset",
- "PermissionSavedID",
- "PermissionSavedInfo",
- "PermissionSavedInfoTime",
- "PermissionSavedNotFoundErrorBody",
- "PermissionSource",
- "PersistentPtyAddedEvent",
- "PersistentPtyAddedEventData",
- "PersistentPtyHandoff",
- "PersistentPtyInfo",
- "PersistentPtyInfoOutput",
- "PersistentPtyInfoSize",
- "PersistentPtyReadLines",
- "PersistentPtyReadResult",
- "PersistentPtyReadResultScreen",
- "PersistentPtyReadResultScreenCursor",
- "PersistentPtyRemovedEvent",
- "PersistentPtyRemovedEventData",
- "PersistentPtySnapshot",
- "PersistentPtySnapshotCursor",
- "PluginCheckResponse",
- "PluginFeatures",
- "PluginInfo",
- "PluginListResponse",
- "PluginSource",
- "PluginSourceBuiltin",
- "PluginSourceLocal",
- "PluginSourcePackage",
- "PluginSourceSdk",
- "PluginState",
- "PluginStateActive",
- "PluginStateFailed",
- "PluginUpdatedEvent",
- "Project",
- "ProjectCommands",
- "ProjectID",
- "ProjectIcon",
- "ProjectNotFoundErrorBody",
- "ProjectTime",
- "ProjectUpdatedEvent",
- "ProjectUpdatedEventData",
- "ProjectVcs",
- "PromptAgentAttachment",
- "PromptBase64",
- "PromptFileAttachment",
- "PromptFileSource",
- "PromptFileSourceInline",
- "PromptFileSourceUri",
- "PromptInputFileAttachment",
- "PromptInputSkillAttachment",
- "PromptMention",
- "PromptSkillAttachment",
- "ProviderCompaction",
- "ProviderCompactionNative",
- "ProviderCompactionSummary",
- "ProviderCompatibility",
- "ProviderEncoding",
- "ProviderGetResponse",
- "ProviderID",
- "ProviderInfo",
- "ProviderListResponse",
- "ProviderNotFoundErrorBody",
- "ProviderRequest",
- "ProviderSettings",
- "ProviderTransport",
- "ProviderUpdatedEvent",
- "Pty",
- "PtyConnectTokenResponse",
- "PtyCreateResponse",
- "PtyCreatedEvent",
- "PtyCreatedEventData",
- "PtyDeletedEvent",
- "PtyDeletedEventData",
- "PtyExitedEvent",
- "PtyExitedEventData",
- "PtyGetResponse",
- "PtyID",
- "PtyListResponse",
- "PtyNotFoundErrorBody",
- "PtyTicketConnectToken",
- "PtyUpdateResponse",
- "PtyUpdateSize",
- "PtyUpdatedEvent",
- "PtyUpdatedEventData",
- "ReferenceGitSource",
- "ReferenceInfo",
- "ReferenceListResponse",
- "ReferenceLocalSource",
- "ReferenceSource",
- "ReferenceUpdatedEvent",
- "RelativePath",
- "RpcErrorBody",
- "RpcEvent",
- "RpcInternalErrorBody",
- "RpcOutput",
- "ServerConnectedEvent",
- "ServerInfo",
- "ServerInfoCapabilities",
- "ServerInfoPaths",
- "ServiceUnavailableErrorBody",
- "SessionActive",
- "SessionAgentSelectedEvent",
- "SessionAgentSelectedEventData",
- "SessionAgentSelectedEventDurable",
- "SessionArchivedEvent",
- "SessionArchivedEventData",
- "SessionArchivedEventDurable",
- "SessionBusyErrorBody",
- "SessionCompactionDeltaEvent",
- "SessionCompactionDeltaEventData",
- "SessionCompactionEndedEvent",
- "SessionCompactionEndedEventData",
- "SessionCompactionEndedEventDurable",
- "SessionCompactionFailedEvent",
- "SessionCompactionFailedEventData",
- "SessionCompactionFailedEventDurable",
- "SessionCompactionStartedEvent",
- "SessionCompactionStartedEventData",
- "SessionCompactionStartedEventDurable",
- "SessionCreatedEvent",
- "SessionCreatedEventData",
- "SessionCreatedEventDurable",
- "SessionDeletedEvent",
- "SessionDeletedEventData",
- "SessionDeletedEventDurable",
- "SessionEventDurable",
- "SessionExecutionFailedEvent",
- "SessionExecutionFailedEventData",
- "SessionExecutionFailedEventDurable",
- "SessionExecutionInterruptedEvent",
- "SessionExecutionInterruptedEventData",
- "SessionExecutionInterruptedEventDurable",
- "SessionExecutionStartedEvent",
- "SessionExecutionStartedEventData",
- "SessionExecutionStartedEventDurable",
- "SessionExecutionSucceededEvent",
- "SessionExecutionSucceededEventData",
- "SessionExecutionSucceededEventDurable",
- "SessionForkBoundary",
- "SessionForkBoundaryBefore",
- "SessionForkBoundaryThrough",
- "SessionForkedEvent",
- "SessionForkedEventData",
- "SessionForkedEventDurable",
- "SessionGenerateResponse",
- "SessionGenerateResponseData",
- "SessionID",
- "SessionIdleEvent",
- "SessionIdleEventData",
- "SessionInboxCancelledEvent",
- "SessionInboxCancelledEventData",
- "SessionInboxCancelledEventDurable",
- "SessionInboxCompaction",
- "SessionInboxCompactionPayload",
- "SessionInboxCompactionTime",
- "SessionInboxDeliveredEvent",
- "SessionInboxDeliveredEventData",
- "SessionInboxDeliveredEventDurable",
- "SessionInboxDelivery",
- "SessionInboxDeliveryChangedEvent",
- "SessionInboxDeliveryChangedEventData",
- "SessionInboxDeliveryChangedEventDurable",
- "SessionInboxEnqueuedEvent",
- "SessionInboxEnqueuedEventData",
- "SessionInboxEnqueuedEventDurable",
- "SessionInboxInfo",
- "SessionInboxItem",
- "SessionInboxItemCompaction",
- "SessionInboxItemMove",
- "SessionInboxItemSynthetic",
- "SessionInboxItemUser",
- "SessionInboxMove",
- "SessionInboxMovePayload",
- "SessionInboxMovePayload1",
- "SessionInboxMoveTime",
- "SessionInboxSynthetic",
- "SessionInboxSyntheticPayload",
- "SessionInboxSyntheticTime",
- "SessionInboxUser",
- "SessionInboxUserPayload",
- "SessionInboxUserTime",
- "SessionInfo",
- "SessionInfoFork",
- "SessionInfoTime",
- "SessionInstructionsUpdatedEvent",
- "SessionInstructionsUpdatedEventData",
- "SessionInstructionsUpdatedEventDurable",
- "SessionInterruptResponse",
- "SessionLogItem",
- "SessionMessageAgentSelected",
- "SessionMessageAgentSelectedTime",
- "SessionMessageAssistant",
- "SessionMessageAssistantContent",
- "SessionMessageAssistantContentItem",
- "SessionMessageAssistantReasoning",
- "SessionMessageAssistantReasoningTime",
- "SessionMessageAssistantRetry",
- "SessionMessageAssistantSnapshot",
- "SessionMessageAssistantText",
- "SessionMessageAssistantTime",
- "SessionMessageAssistantTool",
- "SessionMessageAssistantToolState",
- "SessionMessageAssistantToolTime",
- "SessionMessageCompaction",
- "SessionMessageCompactionCompleted",
- "SessionMessageCompactionCompletedTime",
- "SessionMessageCompactionFailed",
- "SessionMessageCompactionFailedTime",
- "SessionMessageCompactionRunning",
- "SessionMessageCompactionRunningTime",
- "SessionMessageContentUpdatedEvent",
- "SessionMessageContentUpdatedEventData",
- "SessionMessageContentUpdatedEventDurable",
- "SessionMessageID",
- "SessionMessageIdle",
- "SessionMessageIdleTime",
- "SessionMessageInfo",
- "SessionMessageLocationSwitched",
- "SessionMessageLocationSwitchedPrevious",
- "SessionMessageLocationSwitchedTime",
- "SessionMessageModelSelected",
- "SessionMessageModelSelectedTime",
- "SessionMessageNative",
- "SessionMessageShell",
- "SessionMessageShellOutput",
- "SessionMessageShellTime",
- "SessionMessageSkill",
- "SessionMessageSkillTime",
- "SessionMessageSynthetic",
- "SessionMessageSyntheticTime",
- "SessionMessageSystem",
- "SessionMessageSystemTime",
- "SessionMessageToolStateCompleted",
- "SessionMessageToolStateError",
- "SessionMessageToolStateRunning",
- "SessionMessageToolStateStreaming",
- "SessionMessageUser",
- "SessionMessageUserTime",
- "SessionMessagesResponse",
- "SessionMessagesResponseCursor",
- "SessionMetadata",
- "SessionMetadataUpdatedEvent",
- "SessionMetadataUpdatedEventData",
- "SessionMetadataUpdatedEventDurable",
- "SessionModelSelectedEvent",
- "SessionModelSelectedEventData",
- "SessionModelSelectedEventDurable",
- "SessionMovedEvent",
- "SessionMovedEventData",
- "SessionMovedEventDurable",
- "SessionNotFoundErrorBody",
- "SessionPermissionsEvent",
- "SessionPermissionsEventData",
- "SessionPermissionsEventDurable",
- "SessionReasoningDeltaEvent",
- "SessionReasoningDeltaEventData",
- "SessionReasoningEndedEvent",
- "SessionReasoningEndedEventData",
- "SessionReasoningEndedEventDurable",
- "SessionReasoningStartedEvent",
- "SessionReasoningStartedEventData",
- "SessionReasoningStartedEventDurable",
- "SessionRenamedEvent",
- "SessionRenamedEventData",
- "SessionRenamedEventDurable",
- "SessionRetryScheduledEvent",
- "SessionRetryScheduledEventData",
- "SessionRetryScheduledEventDurable",
- "SessionRevert",
- "SessionRevertClearedEvent",
- "SessionRevertClearedEventData",
- "SessionRevertClearedEventDurable",
- "SessionRevertCommittedEvent",
- "SessionRevertCommittedEventData",
- "SessionRevertCommittedEventDurable",
- "SessionRevertStagedEvent",
- "SessionRevertStagedEventData",
- "SessionRevertStagedEventDurable",
- "SessionShellEndedEvent",
- "SessionShellEndedEventData",
- "SessionShellEndedEventDataOutput",
- "SessionShellEndedEventDurable",
- "SessionShellStartedEvent",
- "SessionShellStartedEventData",
- "SessionShellStartedEventDurable",
- "SessionSkillActivatedEvent",
- "SessionSkillActivatedEventData",
- "SessionSkillActivatedEventDurable",
- "SessionStatsActivity",
- "SessionStatsInfo",
- "SessionStatsInfoRange",
- "SessionStatsModelUsage",
- "SessionStatsToolTotals",
- "SessionStatsToolUsage",
- "SessionStatsTools",
- "SessionStatsToolsDetail",
- "SessionStatsToolsNone",
- "SessionStatsToolsSummary",
- "SessionStatus",
- "SessionStatusBusy",
- "SessionStatusIdle",
- "SessionStatusRetry",
- "SessionStatusRetryAction",
- "SessionStatusUpdated",
- "SessionStatusUpdatedData",
- "SessionStepEndedEvent",
- "SessionStepEndedEventData",
- "SessionStepEndedEventDurable",
- "SessionStepFailedEvent",
- "SessionStepFailedEventData",
- "SessionStepFailedEventDurable",
- "SessionStepStartedEvent",
- "SessionStepStartedEventData",
- "SessionStepStartedEventDurable",
- "SessionStepStreamedEvent",
- "SessionStepStreamedEventData",
- "SessionStepStreamedEventDurable",
- "SessionStructuredError",
- "SessionStructuredErrorResponse",
- "SessionSyntheticEvent",
- "SessionSyntheticEventData",
- "SessionSyntheticEventDurable",
- "SessionTextDeltaEvent",
- "SessionTextDeltaEventData",
- "SessionTextEndedEvent",
- "SessionTextEndedEventData",
- "SessionTextEndedEventDurable",
- "SessionTextStartedEvent",
- "SessionTextStartedEventData",
- "SessionTextStartedEventDurable",
- "SessionToolCalledEvent",
- "SessionToolCalledEventData",
- "SessionToolCalledEventDurable",
- "SessionToolFailedEvent",
- "SessionToolFailedEventData",
- "SessionToolFailedEventDurable",
- "SessionToolInputDeltaEvent",
- "SessionToolInputDeltaEventData",
- "SessionToolInputEndedEvent",
- "SessionToolInputEndedEventData",
- "SessionToolInputEndedEventDurable",
- "SessionToolInputStartedEvent",
- "SessionToolInputStartedEventData",
- "SessionToolInputStartedEventDurable",
- "SessionToolProgressEvent",
- "SessionToolProgressEventData",
- "SessionToolSuccessEvent",
- "SessionToolSuccessEventData",
- "SessionToolSuccessEventDurable",
- "SessionTransferData",
- "SessionUnarchivedEvent",
- "SessionUnarchivedEventData",
- "SessionUnarchivedEventDurable",
- "SessionUsageRecordedEvent",
- "SessionUsageRecordedEventData",
- "SessionUsageRecordedEventDurable",
- "SessionUsageUpdatedEvent",
- "SessionUsageUpdatedEventData",
- "SessionViewedEvent",
- "SessionViewedEventData",
- "SessionViewedEventDurable",
- "SessionsCursor",
- "SessionsResponse",
- "SessionsResponseCursor",
- "ShellCreateResponse",
- "ShellCreatedEvent",
- "ShellCreatedEventData",
- "ShellDeletedEvent",
- "ShellDeletedEventData",
- "ShellExitedEvent",
- "ShellExitedEventData",
- "ShellGetResponse",
- "ShellID",
- "ShellInfo",
- "ShellInfoTime",
- "ShellListResponse",
- "ShellNotFoundErrorBody",
- "ShellOutputResponse",
- "ShellOutputResponseData",
- "SkillID",
- "SkillInfo",
- "SkillListResponse",
- "SkillName",
- "SkillNotFoundErrorBody",
- "SkillUpdatedEvent",
- "SnapshotID",
- "TokenUsageInfo",
- "TokenUsageInfoCache",
- "ToolContent",
- "ToolFileContent",
- "ToolTextContent",
- "TuiCommandExecuteEvent",
- "TuiCommandExecuteEventData",
- "TuiPromptAppendEvent",
- "TuiPromptAppendEventData",
- "TuiSessionSelectEvent",
- "TuiSessionSelectEventData",
- "TuiToastShowEvent",
- "TuiToastShowEventData",
- "UnauthorizedErrorBody",
- "UnknownConfigEntry",
- "UnknownConfigInfoMcpServersValue",
- "UnknownConfigModelReasoning",
- "UnknownConnectionInfo",
- "UnknownCredentialValue",
- "UnknownErrorBody",
- "UnknownEvent",
- "UnknownFormField",
- "UnknownFormState",
- "UnknownIntegrationAttemptStatus",
- "UnknownIntegrationCommandAttemptStatus",
- "UnknownIntegrationMethod",
- "UnknownMcpAddConfig",
- "UnknownMcpServerStatus",
- "UnknownPluginSource",
- "UnknownPluginState",
- "UnknownPromptFileSource",
- "UnknownProviderCompaction",
- "UnknownReferenceSource",
- "UnknownSessionEventDurable",
- "UnknownSessionForkBoundary",
- "UnknownSessionInboxInfo",
- "UnknownSessionInboxItem",
- "UnknownSessionMessageAssistantContent",
- "UnknownSessionMessageAssistantContentItem",
- "UnknownSessionMessageAssistantToolState",
- "UnknownSessionMessageCompaction",
- "UnknownSessionMessageInfo",
- "UnknownSessionStatsTools",
- "UnknownSessionStatus",
- "UnknownToolContent",
- "UnknownVoiceTransport",
- "UnknownVoiceTransportInput",
- "VcsBase",
- "VcsBaseResponse",
- "VcsBranch",
- "VcsBranchList",
- "VcsBranchListResponse",
- "VcsBranchUpdatedEvent",
- "VcsBranchUpdatedEventData",
- "VcsDiffResponse",
- "VcsFileStatus",
- "VcsGetResponse",
- "VcsInfo",
- "VcsInitNotSupportedErrorBody",
- "VcsMode",
- "VcsStatusResponse",
- "VoiceSessionResponse",
- "VoiceSessionResponseData",
- "VoiceTransport",
- "VoiceTransportInput",
- "VoiceWebRTCTransport",
- "VoiceWebRTCTransportInput",
- "VoiceWebSocketTransport",
- "VoiceWebSocketTransportInput",
- "WebSearchID",
- "WebSearchProvider",
- "WebSearchResponse",
- "WebSearchResult",
- "WebSearchResultTime",
- "WebsearchProvidersResponse",
- "WebsearchQueryResponse",
- "WebsearchUpdatedEvent",
- "WorkspaceID",
- "WorktreeDirectory",
- "WorktreeErrorBody",
- "WorktreeErrorBodyData",
- "WorktreeInfo",
- "WorktreeList",
- "WorktreeResolvedEvent",
- "WorktreeResolvedEventData",
- "WorktreeResolvedEventDurable",
- "WorktreeUpdatedEvent",
- "WorktreeUpdatedEventData",
-]
+from .vc_get_params import VcGetParams as VcGetParams
+from .mcp_add_params import McpAddParams as McpAddParams
+from .pty_get_params import PtyGetParams as PtyGetParams
+from .vc_base_params import VcBaseParams as VcBaseParams
+from .vc_diff_params import VcDiffParams as VcDiffParams
+from .vc_init_params import VcInitParams as VcInitParams
+from .mcp_list_params import McpListParams as McpListParams
+from .pty_list_params import PtyListParams as PtyListParams
+from .rpc_call_params import RpcCallParams as RpcCallParams
+from .vc_get_response import VcGetResponse as VcGetResponse
+from .agent_get_params import AgentGetParams as AgentGetParams
+from .file_find_params import FileFindParams as FileFindParams
+from .file_list_params import FileListParams as FileListParams
+from .file_read_params import FileReadParams as FileReadParams
+from .form_list_params import FormListParams as FormListParams
+from .pty_get_response import PtyGetResponse as PtyGetResponse
+from .shell_get_params import ShellGetParams as ShellGetParams
+from .vc_base_response import VcBaseResponse as VcBaseResponse
+from .vc_diff_response import VcDiffResponse as VcDiffResponse
+from .vc_status_params import VcStatusParams as VcStatusParams
+from .agent_list_params import AgentListParams as AgentListParams
+from .config_get_params import ConfigGetParams as ConfigGetParams
+from .file_write_params import FileWriteParams as FileWriteParams
+from .mcp_list_response import McpListResponse as McpListResponse
+from .mcp_remove_params import McpRemoveParams as McpRemoveParams
+from .model_list_params import ModelListParams as ModelListParams
+from .pty_create_params import PtyCreateParams as PtyCreateParams
+from .pty_list_response import PtyListResponse as PtyListResponse
+from .pty_remove_params import PtyRemoveParams as PtyRemoveParams
+from .pty_update_params import PtyUpdateParams as PtyUpdateParams
+from .shell_list_params import ShellListParams as ShellListParams
+from .skill_list_params import SkillListParams as SkillListParams
+from .agent_get_response import AgentGetResponse as AgentGetResponse
+from .file_find_response import FileFindResponse as FileFindResponse
+from .file_list_response import FileListResponse as FileListResponse
+from .form_list_response import FormListResponse as FormListResponse
+from .mcp_connect_params import McpConnectParams as McpConnectParams
+from .plugin_list_params import PluginListParams as PluginListParams
+from .session_log_params import SessionLogParams as SessionLogParams
+from .shell_get_response import ShellGetResponse as ShellGetResponse
+from .vc_status_response import VcStatusResponse as VcStatusResponse
+from .agent_list_response import AgentListResponse as AgentListResponse
+from .command_list_params import CommandListParams as CommandListParams
+from .config_get_response import ConfigGetResponse as ConfigGetResponse
+from .file_write_response import FileWriteResponse as FileWriteResponse
+from .location_get_params import LocationGetParams as LocationGetParams
+from .message_list_params import MessageListParams as MessageListParams
+from .model_list_response import ModelListResponse as ModelListResponse
+from .plugin_check_params import PluginCheckParams as PluginCheckParams
+from .project_list_params import ProjectListParams as ProjectListParams
+from .provider_get_params import ProviderGetParams as ProviderGetParams
+from .pty_create_response import PtyCreateResponse as PtyCreateResponse
+from .pty_update_response import PtyUpdateResponse as PtyUpdateResponse
+from .session_diff_params import SessionDiffParams as SessionDiffParams
+from .session_fork_params import SessionForkParams as SessionForkParams
+from .session_list_params import SessionListParams as SessionListParams
+from .session_move_params import SessionMoveParams as SessionMoveParams
+from .session_view_params import SessionViewParams as SessionViewParams
+from .shell_create_params import ShellCreateParams as ShellCreateParams
+from .shell_list_response import ShellListResponse as ShellListResponse
+from .shell_output_params import ShellOutputParams as ShellOutputParams
+from .shell_remove_params import ShellRemoveParams as ShellRemoveParams
+from .skill_list_response import SkillListResponse as SkillListResponse
+from .config_update_params import ConfigUpdateParams as ConfigUpdateParams
+from .generate_text_params import GenerateTextParams as GenerateTextParams
+from .model_default_params import ModelDefaultParams as ModelDefaultParams
+from .plugin_list_response import PluginListResponse as PluginListResponse
+from .plugin_update_params import PluginUpdateParams as PluginUpdateParams
+from .provider_list_params import ProviderListParams as ProviderListParams
+from .session_get_response import SessionGetResponse as SessionGetResponse
+from .session_shell_params import SessionShellParams as SessionShellParams
+from .session_skill_params import SessionSkillParams as SessionSkillParams
+from .session_stats_params import SessionStatsParams as SessionStatsParams
+from .voice_session_params import VoiceSessionParams as VoiceSessionParams
+from .worktree_list_params import WorktreeListParams as WorktreeListParams
+from .command_list_response import CommandListResponse as CommandListResponse
+from .mcp_disconnect_params import McpDisconnectParams as McpDisconnectParams
+from .plugin_check_response import PluginCheckResponse as PluginCheckResponse
+from .project_list_response import ProjectListResponse as ProjectListResponse
+from .project_update_params import ProjectUpdateParams as ProjectUpdateParams
+from .provider_get_response import ProviderGetResponse as ProviderGetResponse
+from .reference_list_params import ReferenceListParams as ReferenceListParams
+from .session_create_params import SessionCreateParams as SessionCreateParams
+from .session_diff_response import SessionDiffResponse as SessionDiffResponse
+from .session_export_params import SessionExportParams as SessionExportParams
+from .session_fork_response import SessionForkResponse as SessionForkResponse
+from .session_prompt_params import SessionPromptParams as SessionPromptParams
+from .session_update_params import SessionUpdateParams as SessionUpdateParams
+from .shell_create_response import ShellCreateResponse as ShellCreateResponse
+from .shell_output_response import ShellOutputResponse as ShellOutputResponse
+from .config_shells_response import ConfigShellsResponse as ConfigShellsResponse
+from .integration_get_params import IntegrationGetParams as IntegrationGetParams
+from .model_default_response import ModelDefaultResponse as ModelDefaultResponse
+from .provider_list_response import ProviderListResponse as ProviderListResponse
+from .session_command_params import SessionCommandParams as SessionCommandParams
+from .session_compact_params import SessionCompactParams as SessionCompactParams
+from .session_import__params import SessionImportParams as SessionImportParams
+from .session_stats_response import SessionStatsResponse as SessionStatsResponse
+from .websearch_query_params import WebsearchQueryParams as WebsearchQueryParams
+from .worktree_create_params import WorktreeCreateParams as WorktreeCreateParams
+from .worktree_remove_params import WorktreeRemoveParams as WorktreeRemoveParams
+from .integration_list_params import IntegrationListParams as IntegrationListParams
+from .permission_get_response import PermissionGetResponse as PermissionGetResponse
+from .permission_reply_params import PermissionReplyParams as PermissionReplyParams
+from .reference_list_response import ReferenceListResponse as ReferenceListResponse
+from .session_active_response import SessionActiveResponse as SessionActiveResponse
+from .session_create_response import SessionCreateResponse as SessionCreateResponse
+from .session_export_response import SessionExportResponse as SessionExportResponse
+from .session_generate_params import SessionGenerateParams as SessionGenerateParams
+from .session_prompt_response import SessionPromptResponse as SessionPromptResponse
+from .worktree_refresh_params import WorktreeRefreshParams as WorktreeRefreshParams
+from .credential_create_params import CredentialCreateParams as CredentialCreateParams
+from .credential_list_response import CredentialListResponse as CredentialListResponse
+from .credential_update_params import CredentialUpdateParams as CredentialUpdateParams
+from .integration_get_response import IntegrationGetResponse as IntegrationGetResponse
+from .permission_create_params import PermissionCreateParams as PermissionCreateParams
+from .permission_list_response import PermissionListResponse as PermissionListResponse
+from .session_compact_response import SessionCompactResponse as SessionCompactResponse
+from .session_context_response import SessionContextResponse as SessionContextResponse
+from .session_import__response import SessionImportResponse as SessionImportResponse
+from .session_interrupt_params import SessionInterruptParams as SessionInterruptParams
+from .session_synthetic_params import SessionSyntheticParams as SessionSyntheticParams
+from .websearch_query_response import WebsearchQueryResponse as WebsearchQueryResponse
+from .integration_list_response import IntegrationListResponse as IntegrationListResponse
+from .credential_create_response import CredentialCreateResponse as CredentialCreateResponse
+from .permission_create_response import PermissionCreateResponse as PermissionCreateResponse
+from .session_environment_params import SessionEnvironmentParams as SessionEnvironmentParams
+from .session_synthetic_response import SessionSyntheticResponse as SessionSyntheticResponse
+from .websearch_providers_params import WebsearchProvidersParams as WebsearchProvidersParams
+from .session_switch_agent_params import SessionSwitchAgentParams as SessionSwitchAgentParams
+from .session_switch_model_params import SessionSwitchModelParams as SessionSwitchModelParams
+from .websearch_providers_response import WebsearchProvidersResponse as WebsearchProvidersResponse
diff --git a/src/opencode/types/agent_get_params.py b/src/opencode/types/agent_get_params.py
new file mode 100644
index 0000000..57313a6
--- /dev/null
+++ b/src/opencode/types/agent_get_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["AgentGetParams", "Location"]
+
+
+class AgentGetParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/agent_get_response.py b/src/opencode/types/agent_get_response.py
new file mode 100644
index 0000000..9ad6fa4
--- /dev/null
+++ b/src/opencode/types/agent_get_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.agent_info import AgentInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["AgentGetResponse"]
+
+
+class AgentGetResponse(BaseModel):
+ data: AgentInfo
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/agent_list_params.py b/src/opencode/types/agent_list_params.py
new file mode 100644
index 0000000..5b6d36a
--- /dev/null
+++ b/src/opencode/types/agent_list_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["AgentListParams", "Location"]
+
+
+class AgentListParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/agent_list_response.py b/src/opencode/types/agent_list_response.py
new file mode 100644
index 0000000..2765461
--- /dev/null
+++ b/src/opencode/types/agent_list_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.agent_info import AgentInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["AgentListResponse"]
+
+
+class AgentListResponse(BaseModel):
+ data: List[AgentInfo]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/command_list_params.py b/src/opencode/types/command_list_params.py
new file mode 100644
index 0000000..f990909
--- /dev/null
+++ b/src/opencode/types/command_list_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["CommandListParams", "Location"]
+
+
+class CommandListParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/command_list_response.py b/src/opencode/types/command_list_response.py
new file mode 100644
index 0000000..1fd81d9
--- /dev/null
+++ b/src/opencode/types/command_list_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.command_info import CommandInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["CommandListResponse"]
+
+
+class CommandListResponse(BaseModel):
+ data: List[CommandInfo]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/config_get_params.py b/src/opencode/types/config_get_params.py
new file mode 100644
index 0000000..49554ad
--- /dev/null
+++ b/src/opencode/types/config_get_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["ConfigGetParams", "Location"]
+
+
+class ConfigGetParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/config_get_response.py b/src/opencode/types/config_get_response.py
new file mode 100644
index 0000000..4e9ff61
--- /dev/null
+++ b/src/opencode/types/config_get_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+from .shared.config_entry import ConfigEntry
+
+__all__ = ["ConfigGetResponse"]
+
+ConfigGetResponse: TypeAlias = List[ConfigEntry]
diff --git a/src/opencode/types/config_shells_response.py b/src/opencode/types/config_shells_response.py
new file mode 100644
index 0000000..d8e46bb
--- /dev/null
+++ b/src/opencode/types/config_shells_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+from .shared.config_shell_option import ConfigShellOption
+
+__all__ = ["ConfigShellsResponse"]
+
+ConfigShellsResponse: TypeAlias = List[ConfigShellOption]
diff --git a/src/opencode/types/config_update_params.py b/src/opencode/types/config_update_params.py
new file mode 100644
index 0000000..09026dc
--- /dev/null
+++ b/src/opencode/types/config_update_params.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, TypedDict
+
+__all__ = ["ConfigUpdateParams"]
+
+
+class ConfigUpdateParams(TypedDict, total=False):
+ shell: Required[Optional[str]]
diff --git a/src/opencode/types/credential_create_params.py b/src/opencode/types/credential_create_params.py
new file mode 100644
index 0000000..f03f283
--- /dev/null
+++ b/src/opencode/types/credential_create_params.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .shared.credential_id import CredentialID
+from .shared.integration_id import IntegrationID
+from .shared.credential_value import CredentialValue
+
+__all__ = ["CredentialCreateParams"]
+
+
+class CredentialCreateParams(TypedDict, total=False):
+ integration_id: Required[Annotated[IntegrationID, PropertyInfo(alias="integrationID")]]
+
+ value: Required[CredentialValue]
+
+ id: CredentialID
+
+ activate: bool
+
+ label: str
diff --git a/src/opencode/types/credential_create_response.py b/src/opencode/types/credential_create_response.py
new file mode 100644
index 0000000..1d5817e
--- /dev/null
+++ b/src/opencode/types/credential_create_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.credential_entry import CredentialEntry
+
+__all__ = ["CredentialCreateResponse"]
+
+
+class CredentialCreateResponse(BaseModel):
+ data: CredentialEntry
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/credential_list_response.py b/src/opencode/types/credential_list_response.py
new file mode 100644
index 0000000..3a9f9ee
--- /dev/null
+++ b/src/opencode/types/credential_list_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.credential_entry import CredentialEntry
+
+__all__ = ["CredentialListResponse"]
+
+
+class CredentialListResponse(BaseModel):
+ data: List[CredentialEntry]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/credential_update_params.py b/src/opencode/types/credential_update_params.py
new file mode 100644
index 0000000..a15dbbd
--- /dev/null
+++ b/src/opencode/types/credential_update_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["CredentialUpdateParams"]
+
+
+class CredentialUpdateParams(TypedDict, total=False):
+ label: Required[str]
diff --git a/src/opencode/types/debug/__init__.py b/src/opencode/types/debug/__init__.py
new file mode 100644
index 0000000..3abe29f
--- /dev/null
+++ b/src/opencode/types/debug/__init__.py
@@ -0,0 +1,6 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .location_evict_params import LocationEvictParams as LocationEvictParams
+from .location_list_response import LocationListResponse as LocationListResponse
diff --git a/src/opencode/types/debug/location_evict_params.py b/src/opencode/types/debug/location_evict_params.py
new file mode 100644
index 0000000..0c89358
--- /dev/null
+++ b/src/opencode/types/debug/location_evict_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["LocationEvictParams", "Location"]
+
+
+class LocationEvictParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/debug/location_list_response.py b/src/opencode/types/debug/location_list_response.py
new file mode 100644
index 0000000..de8b8bc
--- /dev/null
+++ b/src/opencode/types/debug/location_list_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+from ..shared.location_public_ref import LocationPublicRef
+
+__all__ = ["LocationListResponse"]
+
+LocationListResponse: TypeAlias = List[LocationPublicRef]
diff --git a/src/opencode/types/experimental/__init__.py b/src/opencode/types/experimental/__init__.py
new file mode 100644
index 0000000..e0e308f
--- /dev/null
+++ b/src/opencode/types/experimental/__init__.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .persistent_pty_read_params import PersistentPtyReadParams as PersistentPtyReadParams
+from .persistent_pty_get_response import PersistentPtyGetResponse as PersistentPtyGetResponse
+from .persistent_pty_create_params import PersistentPtyCreateParams as PersistentPtyCreateParams
+from .persistent_pty_list_response import PersistentPtyListResponse as PersistentPtyListResponse
+from .persistent_pty_read_response import PersistentPtyReadResponse as PersistentPtyReadResponse
+from .persistent_pty_update_params import PersistentPtyUpdateParams as PersistentPtyUpdateParams
+from .persistent_pty_create_response import PersistentPtyCreateResponse as PersistentPtyCreateResponse
+from .persistent_pty_update_response import PersistentPtyUpdateResponse as PersistentPtyUpdateResponse
+from .persistent_pty_handoff_response import PersistentPtyHandoffResponse as PersistentPtyHandoffResponse
+from .persistent_pty_snapshot_response import PersistentPtySnapshotResponse as PersistentPtySnapshotResponse
+from .persistent_pty_connect_token_response import (
+ PersistentPtyConnectTokenResponse as PersistentPtyConnectTokenResponse,
+)
diff --git a/src/opencode/types/experimental/persistent_pty_connect_token_response.py b/src/opencode/types/experimental/persistent_pty_connect_token_response.py
new file mode 100644
index 0000000..24ebe22
--- /dev/null
+++ b/src/opencode/types/experimental/persistent_pty_connect_token_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.pty_ticket_connect_token import PtyTicketConnectToken
+
+__all__ = ["PersistentPtyConnectTokenResponse"]
+
+
+class PersistentPtyConnectTokenResponse(BaseModel):
+ data: PtyTicketConnectToken
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/experimental/persistent_pty_create_params.py b/src/opencode/types/experimental/persistent_pty_create_params.py
new file mode 100644
index 0000000..71affbd
--- /dev/null
+++ b/src/opencode/types/experimental/persistent_pty_create_params.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union
+from typing_extensions import Required, TypeAlias, TypedDict
+
+from ..._types import SequenceNotStr
+
+__all__ = ["PersistentPtyCreateParams", "Size"]
+
+
+class PersistentPtyCreateParams(TypedDict, total=False):
+ args: Required[SequenceNotStr[str]]
+
+ env: Required[Dict[str, str]]
+
+ title: Required[str]
+
+ command: str
+
+ cwd: str
+
+ size: Size
+
+
+class SizeTyped(TypedDict, total=False):
+ cols: Required[int]
+
+ rows: Required[int]
+
+
+Size: TypeAlias = Union[SizeTyped, Dict[str, object]]
diff --git a/src/opencode/types/experimental/persistent_pty_create_response.py b/src/opencode/types/experimental/persistent_pty_create_response.py
new file mode 100644
index 0000000..f9db328
--- /dev/null
+++ b/src/opencode/types/experimental/persistent_pty_create_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.persistent_pty_info import PersistentPtyInfo
+
+__all__ = ["PersistentPtyCreateResponse"]
+
+
+class PersistentPtyCreateResponse(BaseModel):
+ data: PersistentPtyInfo
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/experimental/persistent_pty_get_response.py b/src/opencode/types/experimental/persistent_pty_get_response.py
new file mode 100644
index 0000000..6ef6a7c
--- /dev/null
+++ b/src/opencode/types/experimental/persistent_pty_get_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.persistent_pty_info import PersistentPtyInfo
+
+__all__ = ["PersistentPtyGetResponse"]
+
+
+class PersistentPtyGetResponse(BaseModel):
+ data: PersistentPtyInfo
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/experimental/persistent_pty_handoff_response.py b/src/opencode/types/experimental/persistent_pty_handoff_response.py
new file mode 100644
index 0000000..f5326b2
--- /dev/null
+++ b/src/opencode/types/experimental/persistent_pty_handoff_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.persistent_pty_handoff import PersistentPtyHandoff
+
+__all__ = ["PersistentPtyHandoffResponse"]
+
+
+class PersistentPtyHandoffResponse(BaseModel):
+ handoff: Optional[PersistentPtyHandoff] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/experimental/persistent_pty_list_response.py b/src/opencode/types/experimental/persistent_pty_list_response.py
new file mode 100644
index 0000000..d9dc363
--- /dev/null
+++ b/src/opencode/types/experimental/persistent_pty_list_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.persistent_pty_info import PersistentPtyInfo
+
+__all__ = ["PersistentPtyListResponse"]
+
+
+class PersistentPtyListResponse(BaseModel):
+ data: List[PersistentPtyInfo]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/experimental/persistent_pty_read_params.py b/src/opencode/types/experimental/persistent_pty_read_params.py
new file mode 100644
index 0000000..ebe2c21
--- /dev/null
+++ b/src/opencode/types/experimental/persistent_pty_read_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+from ..shared.persistent_pty_read_lines import PersistentPtyReadLines
+
+__all__ = ["PersistentPtyReadParams"]
+
+
+class PersistentPtyReadParams(TypedDict, total=False):
+ lines: Optional[PersistentPtyReadLines]
diff --git a/src/opencode/types/experimental/persistent_pty_read_response.py b/src/opencode/types/experimental/persistent_pty_read_response.py
new file mode 100644
index 0000000..e61396b
--- /dev/null
+++ b/src/opencode/types/experimental/persistent_pty_read_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.persistent_pty_read_result import PersistentPtyReadResult
+
+__all__ = ["PersistentPtyReadResponse"]
+
+
+class PersistentPtyReadResponse(BaseModel):
+ data: Optional[PersistentPtyReadResult] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/experimental/persistent_pty_snapshot_response.py b/src/opencode/types/experimental/persistent_pty_snapshot_response.py
new file mode 100644
index 0000000..055a3c3
--- /dev/null
+++ b/src/opencode/types/experimental/persistent_pty_snapshot_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.persistent_pty_snapshot import PersistentPtySnapshot
+
+__all__ = ["PersistentPtySnapshotResponse"]
+
+
+class PersistentPtySnapshotResponse(BaseModel):
+ data: PersistentPtySnapshot
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/experimental/persistent_pty_update_params.py b/src/opencode/types/experimental/persistent_pty_update_params.py
new file mode 100644
index 0000000..4115e8b
--- /dev/null
+++ b/src/opencode/types/experimental/persistent_pty_update_params.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union
+from typing_extensions import Required, Annotated, TypeAlias, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["PersistentPtyUpdateParams", "Size"]
+
+
+class PersistentPtyUpdateParams(TypedDict, total=False):
+ size: Required[Size]
+
+ attachment_id: Annotated[str, PropertyInfo(alias="attachmentID")]
+
+
+class SizeTyped(TypedDict, total=False):
+ cols: Required[int]
+
+ rows: Required[int]
+
+
+Size: TypeAlias = Union[SizeTyped, Dict[str, object]]
diff --git a/src/opencode/types/experimental/persistent_pty_update_response.py b/src/opencode/types/experimental/persistent_pty_update_response.py
new file mode 100644
index 0000000..9f81906
--- /dev/null
+++ b/src/opencode/types/experimental/persistent_pty_update_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.persistent_pty_info import PersistentPtyInfo
+
+__all__ = ["PersistentPtyUpdateResponse"]
+
+
+class PersistentPtyUpdateResponse(BaseModel):
+ data: PersistentPtyInfo
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/file_find_params.py b/src/opencode/types/file_find_params.py
new file mode 100644
index 0000000..5522cc8
--- /dev/null
+++ b/src/opencode/types/file_find_params.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Literal, Required, TypeAlias, TypedDict
+
+__all__ = ["FileFindParams", "Location"]
+
+
+class FileFindParams(TypedDict, total=False):
+ query: Required[str]
+
+ limit: Optional[int]
+
+ location: Optional[Location]
+
+ type: Optional[Literal["file", "directory"]]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/file_find_response.py b/src/opencode/types/file_find_response.py
new file mode 100644
index 0000000..07871e1
--- /dev/null
+++ b/src/opencode/types/file_find_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.file_system_entry import FileSystemEntry
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["FileFindResponse"]
+
+
+class FileFindResponse(BaseModel):
+ data: List[FileSystemEntry]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/file_list_params.py b/src/opencode/types/file_list_params.py
new file mode 100644
index 0000000..b63d38d
--- /dev/null
+++ b/src/opencode/types/file_list_params.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["FileListParams", "Location"]
+
+
+class FileListParams(TypedDict, total=False):
+ location: Optional[Location]
+
+ path: Optional[str]
+ """An absolute path or a path relative to the requested location.
+
+ Defaults to the location directory.
+ """
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/file_list_response.py b/src/opencode/types/file_list_response.py
new file mode 100644
index 0000000..fcaf53e
--- /dev/null
+++ b/src/opencode/types/file_list_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.file_system_entry import FileSystemEntry
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["FileListResponse"]
+
+
+class FileListResponse(BaseModel):
+ data: List[FileSystemEntry]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/file_read_params.py b/src/opencode/types/file_read_params.py
new file mode 100644
index 0000000..5186950
--- /dev/null
+++ b/src/opencode/types/file_read_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["FileReadParams", "Location"]
+
+
+class FileReadParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/file_write_params.py b/src/opencode/types/file_write_params.py
new file mode 100644
index 0000000..1ed3c55
--- /dev/null
+++ b/src/opencode/types/file_write_params.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Required, TypeAlias, TypedDict
+
+__all__ = ["FileWriteParams", "Location"]
+
+
+class FileWriteParams(TypedDict, total=False):
+ path: Required[str]
+ """An absolute path or a path relative to the requested location.
+
+ Missing parent directories are created.
+ """
+
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/file_write_response.py b/src/opencode/types/file_write_response.py
new file mode 100644
index 0000000..bf37a28
--- /dev/null
+++ b/src/opencode/types/file_write_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.file_system_write import FileSystemWrite
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["FileWriteResponse"]
+
+
+class FileWriteResponse(BaseModel):
+ data: FileSystemWrite
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/form_list_params.py b/src/opencode/types/form_list_params.py
new file mode 100644
index 0000000..ba035ee
--- /dev/null
+++ b/src/opencode/types/form_list_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["FormListParams", "Location"]
+
+
+class FormListParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/form_list_response.py b/src/opencode/types/form_list_response.py
new file mode 100644
index 0000000..efd557d
--- /dev/null
+++ b/src/opencode/types/form_list_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.form_info import FormInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["FormListResponse"]
+
+
+class FormListResponse(BaseModel):
+ data: List[FormInfo]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/generate_text_params.py b/src/opencode/types/generate_text_params.py
new file mode 100644
index 0000000..436aff3
--- /dev/null
+++ b/src/opencode/types/generate_text_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, TypedDict
+
+from .shared.model_ref import ModelRef
+
+__all__ = ["GenerateTextParams"]
+
+
+class GenerateTextParams(TypedDict, total=False):
+ prompt: Required[str]
+
+ model: Optional[ModelRef]
diff --git a/src/opencode/types/integration/__init__.py b/src/opencode/types/integration/__init__.py
new file mode 100644
index 0000000..52428b6
--- /dev/null
+++ b/src/opencode/types/integration/__init__.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .connect_key_params import ConnectKeyParams as ConnectKeyParams
+from .oauth_cancel_params import OauthCancelParams as OauthCancelParams
+from .oauth_status_params import OauthStatusParams as OauthStatusParams
+from .oauth_connect_params import OauthConnectParams as OauthConnectParams
+from .wellknown_add_params import WellknownAddParams as WellknownAddParams
+from .command_cancel_params import CommandCancelParams as CommandCancelParams
+from .command_status_params import CommandStatusParams as CommandStatusParams
+from .oauth_complete_params import OauthCompleteParams as OauthCompleteParams
+from .oauth_status_response import OauthStatusResponse as OauthStatusResponse
+from .command_connect_params import CommandConnectParams as CommandConnectParams
+from .oauth_connect_response import OauthConnectResponse as OauthConnectResponse
+from .command_status_response import CommandStatusResponse as CommandStatusResponse
+from .connect_external_params import ConnectExternalParams as ConnectExternalParams
+from .command_connect_response import CommandConnectResponse as CommandConnectResponse
diff --git a/src/opencode/types/integration/command_cancel_params.py b/src/opencode/types/integration/command_cancel_params.py
new file mode 100644
index 0000000..f13611b
--- /dev/null
+++ b/src/opencode/types/integration/command_cancel_params.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Required, Annotated, TypeAlias, TypedDict
+
+from ..._utils import PropertyInfo
+from ..shared.integration_id import IntegrationID
+
+__all__ = ["CommandCancelParams", "Location"]
+
+
+class CommandCancelParams(TypedDict, total=False):
+ integration_id: Required[Annotated[IntegrationID, PropertyInfo(alias="integrationID")]]
+
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/integration/command_connect_params.py b/src/opencode/types/integration/command_connect_params.py
new file mode 100644
index 0000000..9619daa
--- /dev/null
+++ b/src/opencode/types/integration/command_connect_params.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Required, Annotated, TypeAlias, TypedDict
+
+from ..._utils import PropertyInfo
+from ..shared.integration_method_id import IntegrationMethodID
+
+__all__ = ["CommandConnectParams", "Location"]
+
+
+class CommandConnectParams(TypedDict, total=False):
+ method_id: Required[Annotated[IntegrationMethodID, PropertyInfo(alias="methodID")]]
+
+ label: Optional[str]
+
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/integration/command_connect_response.py b/src/opencode/types/integration/command_connect_response.py
new file mode 100644
index 0000000..8011c5f
--- /dev/null
+++ b/src/opencode/types/integration/command_connect_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.location_public_ref import LocationPublicRef
+from ..shared.integration_command_attempt import IntegrationCommandAttempt
+
+__all__ = ["CommandConnectResponse"]
+
+
+class CommandConnectResponse(BaseModel):
+ data: IntegrationCommandAttempt
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/integration/command_status_params.py b/src/opencode/types/integration/command_status_params.py
new file mode 100644
index 0000000..afe5787
--- /dev/null
+++ b/src/opencode/types/integration/command_status_params.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Required, Annotated, TypeAlias, TypedDict
+
+from ..._utils import PropertyInfo
+from ..shared.integration_id import IntegrationID
+
+__all__ = ["CommandStatusParams", "Location"]
+
+
+class CommandStatusParams(TypedDict, total=False):
+ integration_id: Required[Annotated[IntegrationID, PropertyInfo(alias="integrationID")]]
+
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/integration/command_status_response.py b/src/opencode/types/integration/command_status_response.py
new file mode 100644
index 0000000..40921ba
--- /dev/null
+++ b/src/opencode/types/integration/command_status_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.location_public_ref import LocationPublicRef
+from ..shared.integration_command_attempt_status import IntegrationCommandAttemptStatus
+
+__all__ = ["CommandStatusResponse"]
+
+
+class CommandStatusResponse(BaseModel):
+ data: IntegrationCommandAttemptStatus
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/integration/connect_external_params.py b/src/opencode/types/integration/connect_external_params.py
new file mode 100644
index 0000000..bbbb6da
--- /dev/null
+++ b/src/opencode/types/integration/connect_external_params.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Required, Annotated, TypeAlias, TypedDict
+
+from ..._utils import PropertyInfo
+from ..shared.form_answer import FormAnswer
+from ..shared.integration_method_id import IntegrationMethodID
+
+__all__ = ["ConnectExternalParams", "Location"]
+
+
+class ConnectExternalParams(TypedDict, total=False):
+ method_id: Required[Annotated[IntegrationMethodID, PropertyInfo(alias="methodID")]]
+
+ answer: Optional[FormAnswer]
+
+ label: Optional[str]
+
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/integration/connect_key_params.py b/src/opencode/types/integration/connect_key_params.py
new file mode 100644
index 0000000..598a586
--- /dev/null
+++ b/src/opencode/types/integration/connect_key_params.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Required, TypeAlias, TypedDict
+
+from ..shared.form_answer import FormAnswer
+
+__all__ = ["ConnectKeyParams", "Location"]
+
+
+class ConnectKeyParams(TypedDict, total=False):
+ key: Required[str]
+
+ answer: Optional[FormAnswer]
+
+ label: Optional[str]
+
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/integration/oauth_cancel_params.py b/src/opencode/types/integration/oauth_cancel_params.py
new file mode 100644
index 0000000..ea00c0d
--- /dev/null
+++ b/src/opencode/types/integration/oauth_cancel_params.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Required, Annotated, TypeAlias, TypedDict
+
+from ..._utils import PropertyInfo
+from ..shared.integration_id import IntegrationID
+
+__all__ = ["OauthCancelParams", "Location"]
+
+
+class OauthCancelParams(TypedDict, total=False):
+ integration_id: Required[Annotated[IntegrationID, PropertyInfo(alias="integrationID")]]
+
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/integration/oauth_complete_params.py b/src/opencode/types/integration/oauth_complete_params.py
new file mode 100644
index 0000000..5a89f1f
--- /dev/null
+++ b/src/opencode/types/integration/oauth_complete_params.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Required, Annotated, TypeAlias, TypedDict
+
+from ..._utils import PropertyInfo
+from ..shared.integration_id import IntegrationID
+
+__all__ = ["OauthCompleteParams", "Location"]
+
+
+class OauthCompleteParams(TypedDict, total=False):
+ integration_id: Required[Annotated[IntegrationID, PropertyInfo(alias="integrationID")]]
+
+ code: Optional[str]
+
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/integration/oauth_connect_params.py b/src/opencode/types/integration/oauth_connect_params.py
new file mode 100644
index 0000000..109c360
--- /dev/null
+++ b/src/opencode/types/integration/oauth_connect_params.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Required, Annotated, TypeAlias, TypedDict
+
+from ..._utils import PropertyInfo
+from ..shared.form_answer import FormAnswer
+from ..shared.integration_method_id import IntegrationMethodID
+
+__all__ = ["OauthConnectParams", "Location"]
+
+
+class OauthConnectParams(TypedDict, total=False):
+ method_id: Required[Annotated[IntegrationMethodID, PropertyInfo(alias="methodID")]]
+
+ answer: Optional[FormAnswer]
+
+ label: Optional[str]
+
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/integration/oauth_connect_response.py b/src/opencode/types/integration/oauth_connect_response.py
new file mode 100644
index 0000000..741aaa3
--- /dev/null
+++ b/src/opencode/types/integration/oauth_connect_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.integration_attempt import IntegrationAttempt
+from ..shared.location_public_ref import LocationPublicRef
+
+__all__ = ["OauthConnectResponse"]
+
+
+class OauthConnectResponse(BaseModel):
+ data: IntegrationAttempt
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/integration/oauth_status_params.py b/src/opencode/types/integration/oauth_status_params.py
new file mode 100644
index 0000000..3449959
--- /dev/null
+++ b/src/opencode/types/integration/oauth_status_params.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Required, Annotated, TypeAlias, TypedDict
+
+from ..._utils import PropertyInfo
+from ..shared.integration_id import IntegrationID
+
+__all__ = ["OauthStatusParams", "Location"]
+
+
+class OauthStatusParams(TypedDict, total=False):
+ integration_id: Required[Annotated[IntegrationID, PropertyInfo(alias="integrationID")]]
+
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/integration/oauth_status_response.py b/src/opencode/types/integration/oauth_status_response.py
new file mode 100644
index 0000000..fb759bf
--- /dev/null
+++ b/src/opencode/types/integration/oauth_status_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.location_public_ref import LocationPublicRef
+from ..shared.integration_attempt_status import IntegrationAttemptStatus
+
+__all__ = ["OauthStatusResponse"]
+
+
+class OauthStatusResponse(BaseModel):
+ data: IntegrationAttemptStatus
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/integration/wellknown_add_params.py b/src/opencode/types/integration/wellknown_add_params.py
new file mode 100644
index 0000000..5bf7797
--- /dev/null
+++ b/src/opencode/types/integration/wellknown_add_params.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Required, TypeAlias, TypedDict
+
+__all__ = ["WellknownAddParams", "Location"]
+
+
+class WellknownAddParams(TypedDict, total=False):
+ url: Required[str]
+
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/integration_get_params.py b/src/opencode/types/integration_get_params.py
new file mode 100644
index 0000000..9ee61ba
--- /dev/null
+++ b/src/opencode/types/integration_get_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["IntegrationGetParams", "Location"]
+
+
+class IntegrationGetParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/integration_get_response.py b/src/opencode/types/integration_get_response.py
new file mode 100644
index 0000000..aac37b2
--- /dev/null
+++ b/src/opencode/types/integration_get_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.integration_info import IntegrationInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["IntegrationGetResponse"]
+
+
+class IntegrationGetResponse(BaseModel):
+ data: IntegrationInfo
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/integration_list_params.py b/src/opencode/types/integration_list_params.py
new file mode 100644
index 0000000..79076b3
--- /dev/null
+++ b/src/opencode/types/integration_list_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["IntegrationListParams", "Location"]
+
+
+class IntegrationListParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/integration_list_response.py b/src/opencode/types/integration_list_response.py
new file mode 100644
index 0000000..7902ad9
--- /dev/null
+++ b/src/opencode/types/integration_list_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.integration_info import IntegrationInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["IntegrationListResponse"]
+
+
+class IntegrationListResponse(BaseModel):
+ data: List[IntegrationInfo]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/location_get_params.py b/src/opencode/types/location_get_params.py
new file mode 100644
index 0000000..0f37aac
--- /dev/null
+++ b/src/opencode/types/location_get_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["LocationGetParams", "Location"]
+
+
+class LocationGetParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/mcp/__init__.py b/src/opencode/types/mcp/__init__.py
new file mode 100644
index 0000000..1e2ab26
--- /dev/null
+++ b/src/opencode/types/mcp/__init__.py
@@ -0,0 +1,6 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .resource_catalog_params import ResourceCatalogParams as ResourceCatalogParams
+from .resource_catalog_response import ResourceCatalogResponse as ResourceCatalogResponse
diff --git a/src/opencode/types/mcp/resource_catalog_params.py b/src/opencode/types/mcp/resource_catalog_params.py
new file mode 100644
index 0000000..61d5b6b
--- /dev/null
+++ b/src/opencode/types/mcp/resource_catalog_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["ResourceCatalogParams", "Location"]
+
+
+class ResourceCatalogParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/mcp/resource_catalog_response.py b/src/opencode/types/mcp/resource_catalog_response.py
new file mode 100644
index 0000000..9ab8301
--- /dev/null
+++ b/src/opencode/types/mcp/resource_catalog_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.location_public_ref import LocationPublicRef
+from ..shared.mcp_resource_catalog import McpResourceCatalog
+
+__all__ = ["ResourceCatalogResponse"]
+
+
+class ResourceCatalogResponse(BaseModel):
+ data: McpResourceCatalog
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/mcp_add_params.py b/src/opencode/types/mcp_add_params.py
new file mode 100644
index 0000000..33177cd
--- /dev/null
+++ b/src/opencode/types/mcp_add_params.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Required, TypeAlias, TypedDict
+
+from .shared.mcp_local_config import McpLocalConfig
+from .shared.mcp_remote_config import McpRemoteConfig
+
+__all__ = ["McpAddParams", "Config", "Location"]
+
+
+class McpAddParams(TypedDict, total=False):
+ config: Required[Config]
+
+ location: Optional[Location]
+
+
+Config: TypeAlias = Union[McpLocalConfig, McpRemoteConfig]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/mcp_connect_params.py b/src/opencode/types/mcp_connect_params.py
new file mode 100644
index 0000000..7261d16
--- /dev/null
+++ b/src/opencode/types/mcp_connect_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["McpConnectParams", "Location"]
+
+
+class McpConnectParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/mcp_disconnect_params.py b/src/opencode/types/mcp_disconnect_params.py
new file mode 100644
index 0000000..39d2c48
--- /dev/null
+++ b/src/opencode/types/mcp_disconnect_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["McpDisconnectParams", "Location"]
+
+
+class McpDisconnectParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/mcp_list_params.py b/src/opencode/types/mcp_list_params.py
new file mode 100644
index 0000000..7cc9d2c
--- /dev/null
+++ b/src/opencode/types/mcp_list_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["McpListParams", "Location"]
+
+
+class McpListParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/mcp_list_response.py b/src/opencode/types/mcp_list_response.py
new file mode 100644
index 0000000..27004b9
--- /dev/null
+++ b/src/opencode/types/mcp_list_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.mcp_server import McpServer
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["McpListResponse"]
+
+
+class McpListResponse(BaseModel):
+ data: List[McpServer]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/mcp_remove_params.py b/src/opencode/types/mcp_remove_params.py
new file mode 100644
index 0000000..f461f92
--- /dev/null
+++ b/src/opencode/types/mcp_remove_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["McpRemoveParams", "Location"]
+
+
+class McpRemoveParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/message_list_params.py b/src/opencode/types/message_list_params.py
new file mode 100644
index 0000000..28d0ab7
--- /dev/null
+++ b/src/opencode/types/message_list_params.py
@@ -0,0 +1,45 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["MessageListParams"]
+
+
+class MessageListParams(TypedDict, total=False):
+ cursor: Optional[str]
+
+ limit: Optional[int]
+ """Maximum number of messages to return.
+
+ When omitted, the endpoint returns its default page size.
+ """
+
+ order: Optional[Literal["asc", "desc"]]
+ """Message order for the first page.
+
+ Use desc for newest first or asc for oldest first.
+ """
+
+ type: Optional[
+ Literal[
+ "agent-switched",
+ "model-switched",
+ "location-switched",
+ "user",
+ "synthetic",
+ "system",
+ "skill",
+ "shell",
+ "assistant",
+ "compaction",
+ "idle",
+ ]
+ ]
+ """Filter by message type before pagination.
+
+ When omitted, all message types are returned. Pass the same type when following
+ cursors.
+ """
diff --git a/src/opencode/types/migration/__init__.py b/src/opencode/types/migration/__init__.py
new file mode 100644
index 0000000..174cc36
--- /dev/null
+++ b/src/opencode/types/migration/__init__.py
@@ -0,0 +1,5 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .v1_status_response import V1StatusResponse as V1StatusResponse
diff --git a/src/opencode/types/migration/v1_status_response.py b/src/opencode/types/migration/v1_status_response.py
new file mode 100644
index 0000000..22a5b93
--- /dev/null
+++ b/src/opencode/types/migration/v1_status_response.py
@@ -0,0 +1,91 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union, Optional
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = [
+ "V1StatusResponse",
+ "V1StatusResponseVariant0",
+ "V1StatusResponseVariant1",
+ "V1StatusResponseVariant1Progress",
+ "V1StatusResponseVariant2",
+]
+
+
+class V1StatusResponseVariant0(BaseModel):
+ status: Literal["required", "completed"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class V1StatusResponseVariant1Progress(BaseModel):
+ label: str
+
+ denominator: Optional[int] = None
+
+ numerator: Optional[int] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class V1StatusResponseVariant1(BaseModel):
+ progress: V1StatusResponseVariant1Progress
+
+ status: Literal["running"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class V1StatusResponseVariant2(BaseModel):
+ error: str
+
+ status: Literal["error"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+V1StatusResponse: TypeAlias = Union[V1StatusResponseVariant0, V1StatusResponseVariant1, V1StatusResponseVariant2]
diff --git a/src/opencode/types/model_default_params.py b/src/opencode/types/model_default_params.py
new file mode 100644
index 0000000..3702d45
--- /dev/null
+++ b/src/opencode/types/model_default_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["ModelDefaultParams", "Location"]
+
+
+class ModelDefaultParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/model_default_response.py b/src/opencode/types/model_default_response.py
new file mode 100644
index 0000000..f330a93
--- /dev/null
+++ b/src/opencode/types/model_default_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.model_info import ModelInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["ModelDefaultResponse"]
+
+
+class ModelDefaultResponse(BaseModel):
+ data: Optional[ModelInfo] = None
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/model_list_params.py b/src/opencode/types/model_list_params.py
new file mode 100644
index 0000000..3d8b670
--- /dev/null
+++ b/src/opencode/types/model_list_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["ModelListParams", "Location"]
+
+
+class ModelListParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/model_list_response.py b/src/opencode/types/model_list_response.py
new file mode 100644
index 0000000..5f26a6a
--- /dev/null
+++ b/src/opencode/types/model_list_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.model_info import ModelInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["ModelListResponse"]
+
+
+class ModelListResponse(BaseModel):
+ data: List[ModelInfo]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/permission/__init__.py b/src/opencode/types/permission/__init__.py
new file mode 100644
index 0000000..07287d6
--- /dev/null
+++ b/src/opencode/types/permission/__init__.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .saved_list_params import SavedListParams as SavedListParams
+from .request_list_params import RequestListParams as RequestListParams
+from .saved_list_response import SavedListResponse as SavedListResponse
+from .saved_update_params import SavedUpdateParams as SavedUpdateParams
+from .request_list_response import RequestListResponse as RequestListResponse
+from .saved_update_response import SavedUpdateResponse as SavedUpdateResponse
diff --git a/src/opencode/types/permission/request_list_params.py b/src/opencode/types/permission/request_list_params.py
new file mode 100644
index 0000000..7a751fc
--- /dev/null
+++ b/src/opencode/types/permission/request_list_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["RequestListParams", "Location"]
+
+
+class RequestListParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/permission/request_list_response.py b/src/opencode/types/permission/request_list_response.py
new file mode 100644
index 0000000..c74f1da
--- /dev/null
+++ b/src/opencode/types/permission/request_list_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.permission_request import PermissionRequest
+from ..shared.location_public_ref import LocationPublicRef
+
+__all__ = ["RequestListResponse"]
+
+
+class RequestListResponse(BaseModel):
+ data: List[PermissionRequest]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/permission/saved_list_params.py b/src/opencode/types/permission/saved_list_params.py
new file mode 100644
index 0000000..c96aebd
--- /dev/null
+++ b/src/opencode/types/permission/saved_list_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from ..shared.project_id import ProjectID
+
+__all__ = ["SavedListParams"]
+
+
+class SavedListParams(TypedDict, total=False):
+ project_id: Annotated[Optional[ProjectID], PropertyInfo(alias="projectID")]
diff --git a/src/opencode/types/permission/saved_list_response.py b/src/opencode/types/permission/saved_list_response.py
new file mode 100644
index 0000000..6b12737
--- /dev/null
+++ b/src/opencode/types/permission/saved_list_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.permission_saved_info import PermissionSavedInfo
+
+__all__ = ["SavedListResponse"]
+
+
+class SavedListResponse(BaseModel):
+ data: List[PermissionSavedInfo]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/permission/saved_update_params.py b/src/opencode/types/permission/saved_update_params.py
new file mode 100644
index 0000000..3eb48dd
--- /dev/null
+++ b/src/opencode/types/permission/saved_update_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+__all__ = ["SavedUpdateParams"]
+
+
+class SavedUpdateParams(TypedDict, total=False):
+ action: Optional[str]
+
+ resource: Optional[str]
diff --git a/src/opencode/types/permission/saved_update_response.py b/src/opencode/types/permission/saved_update_response.py
new file mode 100644
index 0000000..46a34d3
--- /dev/null
+++ b/src/opencode/types/permission/saved_update_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.permission_saved_info import PermissionSavedInfo
+
+__all__ = ["SavedUpdateResponse"]
+
+
+class SavedUpdateResponse(BaseModel):
+ data: PermissionSavedInfo
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/permission_create_params.py b/src/opencode/types/permission_create_params.py
new file mode 100644
index 0000000..6e21db7
--- /dev/null
+++ b/src/opencode/types/permission_create_params.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Optional
+from typing_extensions import Required, TypedDict
+
+from .._types import SequenceNotStr
+from .shared.agent_id import AgentID
+from .shared.permission_id import PermissionID
+from .shared.permission_source import PermissionSource
+
+__all__ = ["PermissionCreateParams"]
+
+
+class PermissionCreateParams(TypedDict, total=False):
+ action: Required[str]
+
+ resources: Required[SequenceNotStr[str]]
+
+ id: Optional[PermissionID]
+
+ agent: Optional[AgentID]
+
+ metadata: Dict[str, object]
+
+ save: SequenceNotStr[str]
+
+ source: PermissionSource
diff --git a/src/opencode/types/permission_create_response.py b/src/opencode/types/permission_create_response.py
new file mode 100644
index 0000000..74c8a17
--- /dev/null
+++ b/src/opencode/types/permission_create_response.py
@@ -0,0 +1,45 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.permission_id import PermissionID
+from .shared.permission_effect import PermissionEffect
+
+__all__ = ["PermissionCreateResponse", "PermissionCreateResponseData"]
+
+
+class PermissionCreateResponseData(BaseModel):
+ id: PermissionID
+
+ effect: PermissionEffect
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class PermissionCreateResponse(BaseModel):
+ data: PermissionCreateResponseData
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/permission_get_response.py b/src/opencode/types/permission_get_response.py
new file mode 100644
index 0000000..919cc23
--- /dev/null
+++ b/src/opencode/types/permission_get_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.permission_request import PermissionRequest
+
+__all__ = ["PermissionGetResponse"]
+
+
+class PermissionGetResponse(BaseModel):
+ data: PermissionRequest
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/permission_list_response.py b/src/opencode/types/permission_list_response.py
new file mode 100644
index 0000000..7837a3a
--- /dev/null
+++ b/src/opencode/types/permission_list_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.permission_request import PermissionRequest
+
+__all__ = ["PermissionListResponse"]
+
+
+class PermissionListResponse(BaseModel):
+ data: List[PermissionRequest]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/permission_reply_params.py b/src/opencode/types/permission_reply_params.py
new file mode 100644
index 0000000..81b32f7
--- /dev/null
+++ b/src/opencode/types/permission_reply_params.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .shared.session_id import SessionID
+from .shared.permission_reply import PermissionReply
+
+__all__ = ["PermissionReplyParams"]
+
+
+class PermissionReplyParams(TypedDict, total=False):
+ session_id: Required[Annotated[SessionID, PropertyInfo(alias="sessionID")]]
+
+ decision: Required[PermissionReply]
+
+ message: Optional[str]
diff --git a/src/opencode/types/plugin_check_params.py b/src/opencode/types/plugin_check_params.py
new file mode 100644
index 0000000..1b7d575
--- /dev/null
+++ b/src/opencode/types/plugin_check_params.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["PluginCheckParams", "Location"]
+
+
+class PluginCheckParams(TypedDict, total=False):
+ location: Optional[Location]
+
+ target: Optional[str]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/plugin_check_response.py b/src/opencode/types/plugin_check_response.py
new file mode 100644
index 0000000..d6349e0
--- /dev/null
+++ b/src/opencode/types/plugin_check_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.plugin_info import PluginInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["PluginCheckResponse"]
+
+
+class PluginCheckResponse(BaseModel):
+ data: List[PluginInfo]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/plugin_list_params.py b/src/opencode/types/plugin_list_params.py
new file mode 100644
index 0000000..a874298
--- /dev/null
+++ b/src/opencode/types/plugin_list_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["PluginListParams", "Location"]
+
+
+class PluginListParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/plugin_list_response.py b/src/opencode/types/plugin_list_response.py
new file mode 100644
index 0000000..8816ee6
--- /dev/null
+++ b/src/opencode/types/plugin_list_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.plugin_info import PluginInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["PluginListResponse"]
+
+
+class PluginListResponse(BaseModel):
+ data: List[PluginInfo]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/plugin_update_params.py b/src/opencode/types/plugin_update_params.py
new file mode 100644
index 0000000..a7ea845
--- /dev/null
+++ b/src/opencode/types/plugin_update_params.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Required, TypeAlias, TypedDict
+
+from .._types import SequenceNotStr
+
+__all__ = ["PluginUpdateParams", "Location"]
+
+
+class PluginUpdateParams(TypedDict, total=False):
+ targets: Required[SequenceNotStr[str]]
+
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/project_list_params.py b/src/opencode/types/project_list_params.py
new file mode 100644
index 0000000..b00d151
--- /dev/null
+++ b/src/opencode/types/project_list_params.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+__all__ = ["ProjectListParams"]
+
+
+class ProjectListParams(TypedDict, total=False):
+ archived: Optional[bool]
diff --git a/src/opencode/types/project_list_response.py b/src/opencode/types/project_list_response.py
new file mode 100644
index 0000000..5ce077d
--- /dev/null
+++ b/src/opencode/types/project_list_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+from .shared.project import Project
+
+__all__ = ["ProjectListResponse"]
+
+ProjectListResponse: TypeAlias = List[Project]
diff --git a/src/opencode/types/project_update_params.py b/src/opencode/types/project_update_params.py
new file mode 100644
index 0000000..92aa1fd
--- /dev/null
+++ b/src/opencode/types/project_update_params.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+from .shared.project_icon import ProjectIcon
+from .shared.absolute_path import AbsolutePath
+from .shared.project_commands import ProjectCommands
+
+__all__ = ["ProjectUpdateParams"]
+
+
+class ProjectUpdateParams(TypedDict, total=False):
+ canonical: AbsolutePath
+
+ commands: ProjectCommands
+
+ icon: ProjectIcon
+
+ name: str
diff --git a/src/opencode/types/provider_get_params.py b/src/opencode/types/provider_get_params.py
new file mode 100644
index 0000000..7d83311
--- /dev/null
+++ b/src/opencode/types/provider_get_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["ProviderGetParams", "Location"]
+
+
+class ProviderGetParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/provider_get_response.py b/src/opencode/types/provider_get_response.py
new file mode 100644
index 0000000..ba3591b
--- /dev/null
+++ b/src/opencode/types/provider_get_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.provider_info import ProviderInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["ProviderGetResponse"]
+
+
+class ProviderGetResponse(BaseModel):
+ data: ProviderInfo
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/provider_list_params.py b/src/opencode/types/provider_list_params.py
new file mode 100644
index 0000000..2a589b8
--- /dev/null
+++ b/src/opencode/types/provider_list_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["ProviderListParams", "Location"]
+
+
+class ProviderListParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/provider_list_response.py b/src/opencode/types/provider_list_response.py
new file mode 100644
index 0000000..58ddee0
--- /dev/null
+++ b/src/opencode/types/provider_list_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.provider_info import ProviderInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["ProviderListResponse"]
+
+
+class ProviderListResponse(BaseModel):
+ data: List[ProviderInfo]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/pty/__init__.py b/src/opencode/types/pty/__init__.py
new file mode 100644
index 0000000..6c318a0
--- /dev/null
+++ b/src/opencode/types/pty/__init__.py
@@ -0,0 +1,6 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .connect_token_params import ConnectTokenParams as ConnectTokenParams
+from .connect_token_response import ConnectTokenResponse as ConnectTokenResponse
diff --git a/src/opencode/types/pty/connect_token_params.py b/src/opencode/types/pty/connect_token_params.py
new file mode 100644
index 0000000..fdd44fd
--- /dev/null
+++ b/src/opencode/types/pty/connect_token_params.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Annotated, TypeAlias, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["ConnectTokenParams", "Location"]
+
+
+class ConnectTokenParams(TypedDict, total=False):
+ location: Optional[Location]
+
+ x_opencode_ticket: Annotated[Optional[str], PropertyInfo(alias="x-opencode-ticket")]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/pty/connect_token_response.py b/src/opencode/types/pty/connect_token_response.py
new file mode 100644
index 0000000..82980e8
--- /dev/null
+++ b/src/opencode/types/pty/connect_token_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.location_public_ref import LocationPublicRef
+from ..shared.pty_ticket_connect_token import PtyTicketConnectToken
+
+__all__ = ["ConnectTokenResponse"]
+
+
+class ConnectTokenResponse(BaseModel):
+ data: PtyTicketConnectToken
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/pty_create_params.py b/src/opencode/types/pty_create_params.py
new file mode 100644
index 0000000..5a709d2
--- /dev/null
+++ b/src/opencode/types/pty_create_params.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+from .._types import SequenceNotStr
+
+__all__ = ["PtyCreateParams", "Location"]
+
+
+class PtyCreateParams(TypedDict, total=False):
+ args: SequenceNotStr[str]
+
+ command: str
+
+ cwd: str
+
+ env: Dict[str, str]
+
+ location: Optional[Location]
+
+ title: str
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/pty_create_response.py b/src/opencode/types/pty_create_response.py
new file mode 100644
index 0000000..06e62ec
--- /dev/null
+++ b/src/opencode/types/pty_create_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.pty import Pty
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["PtyCreateResponse"]
+
+
+class PtyCreateResponse(BaseModel):
+ data: Pty
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/pty_get_params.py b/src/opencode/types/pty_get_params.py
new file mode 100644
index 0000000..c8e8ff0
--- /dev/null
+++ b/src/opencode/types/pty_get_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["PtyGetParams", "Location"]
+
+
+class PtyGetParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/pty_get_response.py b/src/opencode/types/pty_get_response.py
new file mode 100644
index 0000000..2558d54
--- /dev/null
+++ b/src/opencode/types/pty_get_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.pty import Pty
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["PtyGetResponse"]
+
+
+class PtyGetResponse(BaseModel):
+ data: Pty
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/pty_list_params.py b/src/opencode/types/pty_list_params.py
new file mode 100644
index 0000000..4f56aa7
--- /dev/null
+++ b/src/opencode/types/pty_list_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["PtyListParams", "Location"]
+
+
+class PtyListParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/pty_list_response.py b/src/opencode/types/pty_list_response.py
new file mode 100644
index 0000000..57137a4
--- /dev/null
+++ b/src/opencode/types/pty_list_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.pty import Pty
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["PtyListResponse"]
+
+
+class PtyListResponse(BaseModel):
+ data: List[Pty]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/pty_remove_params.py b/src/opencode/types/pty_remove_params.py
new file mode 100644
index 0000000..66c561d
--- /dev/null
+++ b/src/opencode/types/pty_remove_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["PtyRemoveParams", "Location"]
+
+
+class PtyRemoveParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/pty_update_params.py b/src/opencode/types/pty_update_params.py
new file mode 100644
index 0000000..65921c4
--- /dev/null
+++ b/src/opencode/types/pty_update_params.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Required, TypeAlias, TypedDict
+
+__all__ = ["PtyUpdateParams", "Location", "Size"]
+
+
+class PtyUpdateParams(TypedDict, total=False):
+ location: Optional[Location]
+
+ size: Size
+
+ title: str
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
+
+
+class SizeTyped(TypedDict, total=False):
+ cols: Required[int]
+
+ rows: Required[int]
+
+
+Size: TypeAlias = Union[SizeTyped, Dict[str, object]]
diff --git a/src/opencode/types/pty_update_response.py b/src/opencode/types/pty_update_response.py
new file mode 100644
index 0000000..02eeb6c
--- /dev/null
+++ b/src/opencode/types/pty_update_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.pty import Pty
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["PtyUpdateResponse"]
+
+
+class PtyUpdateResponse(BaseModel):
+ data: Pty
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/reference_list_params.py b/src/opencode/types/reference_list_params.py
new file mode 100644
index 0000000..0ee8a54
--- /dev/null
+++ b/src/opencode/types/reference_list_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["ReferenceListParams", "Location"]
+
+
+class ReferenceListParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/reference_list_response.py b/src/opencode/types/reference_list_response.py
new file mode 100644
index 0000000..8bede76
--- /dev/null
+++ b/src/opencode/types/reference_list_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.reference_info import ReferenceInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["ReferenceListResponse"]
+
+
+class ReferenceListResponse(BaseModel):
+ data: List[ReferenceInfo]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/rpc_call_params.py b/src/opencode/types/rpc_call_params.py
new file mode 100644
index 0000000..75f4cae
--- /dev/null
+++ b/src/opencode/types/rpc_call_params.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Required, Annotated, TypeAlias, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["RpcCallParams", "Location"]
+
+
+class RpcCallParams(TypedDict, total=False):
+ rpc_id: Required[Annotated[str, PropertyInfo(alias="rpcID")]]
+
+ input: object
+
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/session/__init__.py b/src/opencode/types/session/__init__.py
new file mode 100644
index 0000000..928396f
--- /dev/null
+++ b/src/opencode/types/session/__init__.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .form_get_response import FormGetResponse as FormGetResponse
+from .form_reply_params import FormReplyParams as FormReplyParams
+from .form_cancel_params import FormCancelParams as FormCancelParams
+from .form_create_params import FormCreateParams as FormCreateParams
+from .form_list_response import FormListResponse as FormListResponse
+from .inbox_list_response import InboxListResponse as InboxListResponse
+from .inbox_update_params import InboxUpdateParams as InboxUpdateParams
+from .revert_stage_params import RevertStageParams as RevertStageParams
+from .form_create_response import FormCreateResponse as FormCreateResponse
+from .message_get_response import MessageGetResponse as MessageGetResponse
+from .revert_stage_response import RevertStageResponse as RevertStageResponse
diff --git a/src/opencode/types/session/form_cancel_params.py b/src/opencode/types/session/form_cancel_params.py
new file mode 100644
index 0000000..51532c6
--- /dev/null
+++ b/src/opencode/types/session/form_cancel_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["FormCancelParams"]
+
+
+class FormCancelParams(TypedDict, total=False):
+ session_id: Required[Annotated[str, PropertyInfo(alias="sessionID")]]
+
+ message: Optional[str]
diff --git a/src/opencode/types/session/form_create_params.py b/src/opencode/types/session/form_create_params.py
new file mode 100644
index 0000000..9998a84
--- /dev/null
+++ b/src/opencode/types/session/form_create_params.py
@@ -0,0 +1,22 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, TypedDict
+
+from ..shared.form_id import FormID
+from ..shared.form_fields import FormFields
+from ..shared.form_metadata import FormMetadata
+
+__all__ = ["FormCreateParams"]
+
+
+class FormCreateParams(TypedDict, total=False):
+ fields: Required[FormFields]
+
+ title: Required[str]
+
+ id: Optional[FormID]
+
+ metadata: FormMetadata
diff --git a/src/opencode/types/session/form_create_response.py b/src/opencode/types/session/form_create_response.py
new file mode 100644
index 0000000..18c6602
--- /dev/null
+++ b/src/opencode/types/session/form_create_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.form_info import FormInfo
+
+__all__ = ["FormCreateResponse"]
+
+
+class FormCreateResponse(BaseModel):
+ data: FormInfo
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session/form_get_response.py b/src/opencode/types/session/form_get_response.py
new file mode 100644
index 0000000..caeec6e
--- /dev/null
+++ b/src/opencode/types/session/form_get_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.form_detail import FormDetail
+
+__all__ = ["FormGetResponse"]
+
+
+class FormGetResponse(BaseModel):
+ data: FormDetail
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session/form_list_response.py b/src/opencode/types/session/form_list_response.py
new file mode 100644
index 0000000..94b4594
--- /dev/null
+++ b/src/opencode/types/session/form_list_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.form_info import FormInfo
+
+__all__ = ["FormListResponse"]
+
+
+class FormListResponse(BaseModel):
+ data: List[FormInfo]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session/form_reply_params.py b/src/opencode/types/session/form_reply_params.py
new file mode 100644
index 0000000..5ef6eb4
--- /dev/null
+++ b/src/opencode/types/session/form_reply_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from ..shared.form_answer import FormAnswer
+
+__all__ = ["FormReplyParams"]
+
+
+class FormReplyParams(TypedDict, total=False):
+ session_id: Required[Annotated[str, PropertyInfo(alias="sessionID")]]
+
+ answer: Required[FormAnswer]
diff --git a/src/opencode/types/session/inbox_list_response.py b/src/opencode/types/session/inbox_list_response.py
new file mode 100644
index 0000000..65ae759
--- /dev/null
+++ b/src/opencode/types/session/inbox_list_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.session_inbox_info import SessionInboxInfo
+
+__all__ = ["InboxListResponse"]
+
+
+class InboxListResponse(BaseModel):
+ data: List[SessionInboxInfo]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session/inbox_update_params.py b/src/opencode/types/session/inbox_update_params.py
new file mode 100644
index 0000000..38c9a3d
--- /dev/null
+++ b/src/opencode/types/session/inbox_update_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from ..shared.session_id import SessionID
+from ..shared.session_inbox_delivery import SessionInboxDelivery
+
+__all__ = ["InboxUpdateParams"]
+
+
+class InboxUpdateParams(TypedDict, total=False):
+ session_id: Required[Annotated[SessionID, PropertyInfo(alias="sessionID")]]
+
+ delivery: Required[SessionInboxDelivery]
diff --git a/src/opencode/types/session/instructions/__init__.py b/src/opencode/types/session/instructions/__init__.py
new file mode 100644
index 0000000..77416b8
--- /dev/null
+++ b/src/opencode/types/session/instructions/__init__.py
@@ -0,0 +1,6 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .entry_put_params import EntryPutParams as EntryPutParams
+from .entry_list_response import EntryListResponse as EntryListResponse
diff --git a/src/opencode/types/session/instructions/entry_list_response.py b/src/opencode/types/session/instructions/entry_list_response.py
new file mode 100644
index 0000000..9572cbb
--- /dev/null
+++ b/src/opencode/types/session/instructions/entry_list_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+from ...shared.instruction_entry_info import InstructionEntryInfo
+
+__all__ = ["EntryListResponse"]
+
+
+class EntryListResponse(BaseModel):
+ data: List[InstructionEntryInfo]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session/instructions/entry_put_params.py b/src/opencode/types/session/instructions/entry_put_params.py
new file mode 100644
index 0000000..f04c0ad
--- /dev/null
+++ b/src/opencode/types/session/instructions/entry_put_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+from ...shared.session_id import SessionID
+
+__all__ = ["EntryPutParams"]
+
+
+class EntryPutParams(TypedDict, total=False):
+ session_id: Required[Annotated[SessionID, PropertyInfo(alias="sessionID")]]
+
+ value: Required[object]
diff --git a/src/opencode/types/session/message_get_response.py b/src/opencode/types/session/message_get_response.py
new file mode 100644
index 0000000..e6e3994
--- /dev/null
+++ b/src/opencode/types/session/message_get_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.session_message_info import SessionMessageInfo
+
+__all__ = ["MessageGetResponse"]
+
+
+class MessageGetResponse(BaseModel):
+ data: SessionMessageInfo
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session/revert_stage_params.py b/src/opencode/types/session/revert_stage_params.py
new file mode 100644
index 0000000..14a6e9a
--- /dev/null
+++ b/src/opencode/types/session/revert_stage_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from ..shared.session_message_id import SessionMessageID
+
+__all__ = ["RevertStageParams"]
+
+
+class RevertStageParams(TypedDict, total=False):
+ message_id: Required[Annotated[SessionMessageID, PropertyInfo(alias="messageID")]]
+
+ files: Optional[bool]
diff --git a/src/opencode/types/session/revert_stage_response.py b/src/opencode/types/session/revert_stage_response.py
new file mode 100644
index 0000000..33c8a28
--- /dev/null
+++ b/src/opencode/types/session/revert_stage_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.session_revert import SessionRevert
+
+__all__ = ["RevertStageResponse"]
+
+
+class RevertStageResponse(BaseModel):
+ data: SessionRevert
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_active_response.py b/src/opencode/types/session_active_response.py
new file mode 100644
index 0000000..9f7f142
--- /dev/null
+++ b/src/opencode/types/session_active_response.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["SessionActiveResponse"]
+
+
+class SessionActiveResponse(BaseModel):
+ data: Dict[str, object]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_command_params.py b/src/opencode/types/session_command_params.py
new file mode 100644
index 0000000..c07e5c2
--- /dev/null
+++ b/src/opencode/types/session_command_params.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable, Optional
+from typing_extensions import Required, TypedDict
+
+from .shared.session_inbox_delivery import SessionInboxDelivery
+from .shared.prompt_agent_attachment import PromptAgentAttachment
+from .shared.prompt_input_file_attachment import PromptInputFileAttachment
+from .shared.prompt_input_skill_attachment import PromptInputSkillAttachment
+
+__all__ = ["SessionCommandParams"]
+
+
+class SessionCommandParams(TypedDict, total=False):
+ name: Required[str]
+
+ text: Required[str]
+
+ agents: Iterable[PromptAgentAttachment]
+
+ delivery: Optional[SessionInboxDelivery]
+
+ files: Iterable[PromptInputFileAttachment]
+
+ skills: Iterable[PromptInputSkillAttachment]
diff --git a/src/opencode/types/session_compact_params.py b/src/opencode/types/session_compact_params.py
new file mode 100644
index 0000000..4909f4f
--- /dev/null
+++ b/src/opencode/types/session_compact_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+from .shared.session_message_id import SessionMessageID
+from .shared.session_inbox_delivery import SessionInboxDelivery
+
+__all__ = ["SessionCompactParams"]
+
+
+class SessionCompactParams(TypedDict, total=False):
+ id: Optional[SessionMessageID]
+
+ delivery: Optional[SessionInboxDelivery]
diff --git a/src/opencode/types/session_compact_response.py b/src/opencode/types/session_compact_response.py
new file mode 100644
index 0000000..95b6aaf
--- /dev/null
+++ b/src/opencode/types/session_compact_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.session_inbox_compaction import SessionInboxCompaction
+
+__all__ = ["SessionCompactResponse"]
+
+
+class SessionCompactResponse(BaseModel):
+ data: SessionInboxCompaction
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_context_response.py b/src/opencode/types/session_context_response.py
new file mode 100644
index 0000000..a487414
--- /dev/null
+++ b/src/opencode/types/session_context_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.session_message_info import SessionMessageInfo
+
+__all__ = ["SessionContextResponse"]
+
+
+class SessionContextResponse(BaseModel):
+ data: List[SessionMessageInfo]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_create_params.py b/src/opencode/types/session_create_params.py
new file mode 100644
index 0000000..5e5b053
--- /dev/null
+++ b/src/opencode/types/session_create_params.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .shared.agent_id import AgentID
+from .shared.model_ref import ModelRef
+from .shared.session_id import SessionID
+from .shared.session_metadata import SessionMetadata
+from .shared.permission_ruleset import PermissionRuleset
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["SessionCreateParams"]
+
+
+class SessionCreateParams(TypedDict, total=False):
+ id: Optional[SessionID]
+
+ agent: Optional[AgentID]
+
+ location: Optional[LocationPublicRef]
+
+ metadata: Optional[SessionMetadata]
+
+ model: Optional[ModelRef]
+
+ parent_id: Annotated[Optional[SessionID], PropertyInfo(alias="parentID")]
+
+ permissions: Optional[PermissionRuleset]
+
+ title: Optional[str]
diff --git a/src/opencode/types/session_create_response.py b/src/opencode/types/session_create_response.py
new file mode 100644
index 0000000..ab008e7
--- /dev/null
+++ b/src/opencode/types/session_create_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.session_info import SessionInfo
+
+__all__ = ["SessionCreateResponse"]
+
+
+class SessionCreateResponse(BaseModel):
+ data: SessionInfo
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_diff_params.py b/src/opencode/types/session_diff_params.py
new file mode 100644
index 0000000..2ac4d7e
--- /dev/null
+++ b/src/opencode/types/session_diff_params.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .shared.session_message_id import SessionMessageID
+
+__all__ = ["SessionDiffParams"]
+
+
+class SessionDiffParams(TypedDict, total=False):
+ context: Optional[int]
+ """Unchanged lines around each hunk. Omit for full-file patches."""
+
+ from_: Annotated[Optional[SessionMessageID], PropertyInfo(alias="from")]
+ """User message whose turn to diff.
+
+ Defaults to the turn of the newest user message.
+ """
+
+ to: Optional[SessionMessageID]
+ """Later user message whose turn ends the range.
+
+ Defaults to the turn of `from` alone.
+ """
diff --git a/src/opencode/types/session_diff_response.py b/src/opencode/types/session_diff_response.py
new file mode 100644
index 0000000..db89d3f
--- /dev/null
+++ b/src/opencode/types/session_diff_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.file_diff_info import FileDiffInfo
+
+__all__ = ["SessionDiffResponse"]
+
+
+class SessionDiffResponse(BaseModel):
+ data: List[FileDiffInfo]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_environment_params.py b/src/opencode/types/session_environment_params.py
new file mode 100644
index 0000000..6893db4
--- /dev/null
+++ b/src/opencode/types/session_environment_params.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict
+from typing_extensions import Required, TypedDict
+
+__all__ = ["SessionEnvironmentParams"]
+
+
+class SessionEnvironmentParams(TypedDict, total=False):
+ variables: Required[Dict[str, str]]
diff --git a/src/opencode/types/session_export_params.py b/src/opencode/types/session_export_params.py
new file mode 100644
index 0000000..1eba773
--- /dev/null
+++ b/src/opencode/types/session_export_params.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+__all__ = ["SessionExportParams"]
+
+
+class SessionExportParams(TypedDict, total=False):
+ sanitize: Optional[bool]
diff --git a/src/opencode/types/session_export_response.py b/src/opencode/types/session_export_response.py
new file mode 100644
index 0000000..923a456
--- /dev/null
+++ b/src/opencode/types/session_export_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.session_transfer_data import SessionTransferData
+
+__all__ = ["SessionExportResponse"]
+
+
+class SessionExportResponse(BaseModel):
+ data: SessionTransferData
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_fork_params.py b/src/opencode/types/session_fork_params.py
new file mode 100644
index 0000000..71ae45c
--- /dev/null
+++ b/src/opencode/types/session_fork_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+from .shared.session_message_id import SessionMessageID
+
+__all__ = ["SessionForkParams"]
+
+
+class SessionForkParams(TypedDict, total=False):
+ before: Optional[SessionMessageID]
diff --git a/src/opencode/types/session_fork_response.py b/src/opencode/types/session_fork_response.py
new file mode 100644
index 0000000..79b09b3
--- /dev/null
+++ b/src/opencode/types/session_fork_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.session_info import SessionInfo
+
+__all__ = ["SessionForkResponse"]
+
+
+class SessionForkResponse(BaseModel):
+ data: SessionInfo
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_generate_params.py b/src/opencode/types/session_generate_params.py
new file mode 100644
index 0000000..1d89970
--- /dev/null
+++ b/src/opencode/types/session_generate_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["SessionGenerateParams"]
+
+
+class SessionGenerateParams(TypedDict, total=False):
+ prompt: Required[str]
diff --git a/src/opencode/types/session_get_response.py b/src/opencode/types/session_get_response.py
new file mode 100644
index 0000000..0aa01b5
--- /dev/null
+++ b/src/opencode/types/session_get_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.session_info import SessionInfo
+
+__all__ = ["SessionGetResponse"]
+
+
+class SessionGetResponse(BaseModel):
+ data: SessionInfo
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_import__params.py b/src/opencode/types/session_import__params.py
new file mode 100644
index 0000000..121cf2a
--- /dev/null
+++ b/src/opencode/types/session_import__params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable, Optional
+from typing_extensions import Required, TypedDict
+
+from .shared.session_info import SessionInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["SessionImportParams"]
+
+
+class SessionImportParams(TypedDict, total=False):
+ info: Required[SessionInfo]
+
+ messages: Required[Iterable[object]]
+
+ location: Optional[LocationPublicRef]
diff --git a/src/opencode/types/session_import__response.py b/src/opencode/types/session_import__response.py
new file mode 100644
index 0000000..2dd408f
--- /dev/null
+++ b/src/opencode/types/session_import__response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.session_info import SessionInfo
+
+__all__ = ["SessionImportResponse"]
+
+
+class SessionImportResponse(BaseModel):
+ data: SessionInfo
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_interrupt_params.py b/src/opencode/types/session_interrupt_params.py
new file mode 100644
index 0000000..52b5073
--- /dev/null
+++ b/src/opencode/types/session_interrupt_params.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+__all__ = ["SessionInterruptParams"]
+
+
+class SessionInterruptParams(TypedDict, total=False):
+ resume: Optional[bool]
diff --git a/src/opencode/types/session_list_params.py b/src/opencode/types/session_list_params.py
new file mode 100644
index 0000000..8ac3046
--- /dev/null
+++ b/src/opencode/types/session_list_params.py
@@ -0,0 +1,40 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Literal, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .shared.project_id import ProjectID
+from .shared.session_id import SessionID
+from .shared.absolute_path import AbsolutePath
+from .shared.relative_path import RelativePath
+from .shared.sessions_cursor import SessionsCursor
+
+__all__ = ["SessionListParams"]
+
+
+class SessionListParams(TypedDict, total=False):
+ archived: Optional[bool]
+
+ cursor: Optional[SessionsCursor]
+
+ directory: Optional[AbsolutePath]
+
+ limit: Optional[int]
+ """Maximum number of sessions to return. Defaults to the newest 50 sessions."""
+
+ order: Optional[Literal["asc", "desc"]]
+ """Session order for the first page.
+
+ Use desc for newest first or asc for oldest first.
+ """
+
+ parent_id: Annotated[Optional[SessionID], PropertyInfo(alias="parentID")]
+
+ project: Optional[ProjectID]
+
+ search: Optional[str]
+
+ subpath: Optional[RelativePath]
diff --git a/src/opencode/types/session_log_params.py b/src/opencode/types/session_log_params.py
new file mode 100644
index 0000000..1c81028
--- /dev/null
+++ b/src/opencode/types/session_log_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+from .shared.event_seq import EventSeq
+
+__all__ = ["SessionLogParams"]
+
+
+class SessionLogParams(TypedDict, total=False):
+ after: Optional[EventSeq]
+
+ follow: Optional[bool]
diff --git a/src/opencode/types/session_move_params.py b/src/opencode/types/session_move_params.py
new file mode 100644
index 0000000..2ab282e
--- /dev/null
+++ b/src/opencode/types/session_move_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, TypedDict
+
+from .shared.absolute_path import AbsolutePath
+from .shared.session_inbox_delivery import SessionInboxDelivery
+
+__all__ = ["SessionMoveParams"]
+
+
+class SessionMoveParams(TypedDict, total=False):
+ directory: Required[AbsolutePath]
+
+ delivery: Optional[SessionInboxDelivery]
diff --git a/src/opencode/types/session_prompt_params.py b/src/opencode/types/session_prompt_params.py
new file mode 100644
index 0000000..26e32b3
--- /dev/null
+++ b/src/opencode/types/session_prompt_params.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Iterable, Optional
+from typing_extensions import Required, TypedDict
+
+from .shared.session_message_id import SessionMessageID
+from .shared.session_inbox_delivery import SessionInboxDelivery
+from .shared.prompt_agent_attachment import PromptAgentAttachment
+from .shared.prompt_input_file_attachment import PromptInputFileAttachment
+from .shared.prompt_input_skill_attachment import PromptInputSkillAttachment
+
+__all__ = ["SessionPromptParams"]
+
+
+class SessionPromptParams(TypedDict, total=False):
+ text: Required[str]
+
+ id: Optional[SessionMessageID]
+
+ agents: Iterable[PromptAgentAttachment]
+
+ delivery: Optional[SessionInboxDelivery]
+
+ files: Iterable[PromptInputFileAttachment]
+
+ metadata: Dict[str, object]
+
+ resume: Optional[bool]
+
+ skills: Iterable[PromptInputSkillAttachment]
diff --git a/src/opencode/types/session_prompt_response.py b/src/opencode/types/session_prompt_response.py
new file mode 100644
index 0000000..0ae6175
--- /dev/null
+++ b/src/opencode/types/session_prompt_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.session_inbox_user import SessionInboxUser
+
+__all__ = ["SessionPromptResponse"]
+
+
+class SessionPromptResponse(BaseModel):
+ data: SessionInboxUser
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_shell_params.py b/src/opencode/types/session_shell_params.py
new file mode 100644
index 0000000..8dbd4f8
--- /dev/null
+++ b/src/opencode/types/session_shell_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, TypedDict
+
+from .shared.session_message_id import SessionMessageID
+
+__all__ = ["SessionShellParams"]
+
+
+class SessionShellParams(TypedDict, total=False):
+ command: Required[str]
+
+ id: Optional[SessionMessageID]
diff --git a/src/opencode/types/session_skill_params.py b/src/opencode/types/session_skill_params.py
new file mode 100644
index 0000000..2fe9cf7
--- /dev/null
+++ b/src/opencode/types/session_skill_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Required, TypedDict
+
+from .shared.skill_id import SkillID
+
+__all__ = ["SessionSkillParams"]
+
+
+class SessionSkillParams(TypedDict, total=False):
+ id: Required[SkillID]
+
+ resume: Optional[bool]
diff --git a/src/opencode/types/session_stats_params.py b/src/opencode/types/session_stats_params.py
new file mode 100644
index 0000000..4336ea5
--- /dev/null
+++ b/src/opencode/types/session_stats_params.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Optional
+from typing_extensions import Literal, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .shared.project_id import ProjectID
+
+__all__ = ["SessionStatsParams"]
+
+
+class SessionStatsParams(TypedDict, total=False):
+ from_: Annotated[Union[Literal["Infinity", "-Infinity", "NaN"], float, None], PropertyInfo(alias="from")]
+
+ project: Optional[ProjectID]
+
+ timezone: Optional[str]
+
+ to: Union[Literal["Infinity", "-Infinity", "NaN"], float, None]
+
+ tools: Optional[Literal["none", "summary", "detail"]]
diff --git a/src/opencode/types/session_stats_response.py b/src/opencode/types/session_stats_response.py
new file mode 100644
index 0000000..3f665c7
--- /dev/null
+++ b/src/opencode/types/session_stats_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.session_stats_info import SessionStatsInfo
+
+__all__ = ["SessionStatsResponse"]
+
+
+class SessionStatsResponse(BaseModel):
+ data: SessionStatsInfo
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_switch_agent_params.py b/src/opencode/types/session_switch_agent_params.py
new file mode 100644
index 0000000..23974de
--- /dev/null
+++ b/src/opencode/types/session_switch_agent_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+from .shared.agent_id import AgentID
+
+__all__ = ["SessionSwitchAgentParams"]
+
+
+class SessionSwitchAgentParams(TypedDict, total=False):
+ agent: Required[AgentID]
diff --git a/src/opencode/types/session_switch_model_params.py b/src/opencode/types/session_switch_model_params.py
new file mode 100644
index 0000000..e03faf1
--- /dev/null
+++ b/src/opencode/types/session_switch_model_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+from .shared.model_ref import ModelRef
+
+__all__ = ["SessionSwitchModelParams"]
+
+
+class SessionSwitchModelParams(TypedDict, total=False):
+ model: Required[ModelRef]
diff --git a/src/opencode/types/session_synthetic_params.py b/src/opencode/types/session_synthetic_params.py
new file mode 100644
index 0000000..a1b29f9
--- /dev/null
+++ b/src/opencode/types/session_synthetic_params.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Optional
+from typing_extensions import Required, TypedDict
+
+from .shared.session_message_id import SessionMessageID
+from .shared.session_inbox_delivery import SessionInboxDelivery
+
+__all__ = ["SessionSyntheticParams"]
+
+
+class SessionSyntheticParams(TypedDict, total=False):
+ text: Required[str]
+
+ id: Optional[SessionMessageID]
+
+ delivery: Optional[SessionInboxDelivery]
+
+ description: Optional[str]
+
+ metadata: Dict[str, object]
+
+ resume: Optional[bool]
diff --git a/src/opencode/types/session_synthetic_response.py b/src/opencode/types/session_synthetic_response.py
new file mode 100644
index 0000000..0d346a8
--- /dev/null
+++ b/src/opencode/types/session_synthetic_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.session_inbox_synthetic import SessionInboxSynthetic
+
+__all__ = ["SessionSyntheticResponse"]
+
+
+class SessionSyntheticResponse(BaseModel):
+ data: SessionInboxSynthetic
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_update_params.py b/src/opencode/types/session_update_params.py
new file mode 100644
index 0000000..637166e
--- /dev/null
+++ b/src/opencode/types/session_update_params.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+from .shared.session_metadata import SessionMetadata
+from .shared.permission_ruleset import PermissionRuleset
+
+__all__ = ["SessionUpdateParams"]
+
+
+class SessionUpdateParams(TypedDict, total=False):
+ archived: Optional[bool]
+ """Archive the session with true or restore it with false.
+
+ Repeating the current state is a no-op.
+ """
+
+ metadata: Optional[SessionMetadata]
+
+ permissions: Optional[PermissionRuleset]
+
+ title: Optional[str]
diff --git a/src/opencode/types/session_view_params.py b/src/opencode/types/session_view_params.py
new file mode 100644
index 0000000..6064516
--- /dev/null
+++ b/src/opencode/types/session_view_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["SessionViewParams"]
+
+
+class SessionViewParams(TypedDict, total=False):
+ idle: Required[float]
diff --git a/src/opencode/types/shared/__init__.py b/src/opencode/types/shared/__init__.py
new file mode 100644
index 0000000..53bd0a0
--- /dev/null
+++ b/src/opencode/types/shared/__init__.py
@@ -0,0 +1,362 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from .pty import Pty as Pty
+from .event import Event as Event
+from .pty_id import PtyID as PtyID
+from .form_id import FormID as FormID
+from .project import Project as Project
+from .agent_id import AgentID as AgentID
+from .event_id import EventID as EventID
+from .shell_id import ShellID as ShellID
+from .skill_id import SkillID as SkillID
+from .vcs_base import VcsBase as VcsBase
+from .vcs_info import VcsInfo as VcsInfo
+from .vcs_mode import VcsMode as VcsMode
+from .event_seq import EventSeq as EventSeq
+from .form_info import FormInfo as FormInfo
+from .form_when import FormWhen as FormWhen
+from .model_ref import ModelRef as ModelRef
+from .money_usd import MoneyUSD as MoneyUSD
+from .rpc_event import RpcEvent as RpcEvent
+from .agent_info import AgentInfo as AgentInfo
+from .form_field import FormField as FormField
+from .form_state import FormState as FormState
+from .form_value import FormValue as FormValue
+from .mcp_server import McpServer as McpServer
+from .model_cost import ModelCost as ModelCost
+from .model_info import ModelInfo as ModelInfo
+from .project_id import ProjectID as ProjectID
+from .rpc_output import RpcOutput as RpcOutput
+from .session_id import SessionID as SessionID
+from .shell_info import ShellInfo as ShellInfo
+from .skill_info import SkillInfo as SkillInfo
+from .skill_name import SkillName as SkillName
+from .vcs_branch import VcsBranch as VcsBranch
+from .agent_color import AgentColor as AgentColor
+from .config_info import ConfigInfo as ConfigInfo
+from .form_answer import FormAnswer as FormAnswer
+from .form_detail import FormDetail as FormDetail
+from .form_fields import FormFields as FormFields
+from .form_option import FormOption as FormOption
+from .plugin_info import PluginInfo as PluginInfo
+from .project_vcs import ProjectVcs as ProjectVcs
+from .provider_id import ProviderID as ProviderID
+from .server_info import ServerInfo as ServerInfo
+from .session_id1 import SessionID1 as SessionID1
+from .snapshot_id import SnapshotID as SnapshotID
+from .command_info import CommandInfo as CommandInfo
+from .config_agent import ConfigAgent as ConfigAgent
+from .config_entry import ConfigEntry as ConfigEntry
+from .config_model import ConfigModel as ConfigModel
+from .form_fields1 import FormFields1 as FormFields1
+from .form_fields2 import FormFields2 as FormFields2
+from .form_fields3 import FormFields3 as FormFields3
+from .location_ref import LocationRef as LocationRef
+from .mcp_protocol import McpProtocol as McpProtocol
+from .mcp_resource import McpResource as McpResource
+from .pairing_code import PairingCode as PairingCode
+from .plugin_state import PluginState as PluginState
+from .project_icon import ProjectIcon as ProjectIcon
+from .project_time import ProjectTime as ProjectTime
+from .session_info import SessionInfo as SessionInfo
+from .tool_content import ToolContent as ToolContent
+from .workspace_id import WorkspaceID as WorkspaceID
+from .absolute_path import AbsolutePath as AbsolutePath
+from .credential_id import CredentialID as CredentialID
+from .event_version import EventVersion as EventVersion
+from .form_metadata import FormMetadata as FormMetadata
+from .model_variant import ModelVariant as ModelVariant
+from .permission_id import PermissionID as PermissionID
+from .plugin_source import PluginSource as PluginSource
+from .prompt_base64 import PromptBase64 as PromptBase64
+from .provider_info import ProviderInfo as ProviderInfo
+from .relative_path import RelativePath as RelativePath
+from .web_search_id import WebSearchID as WebSearchID
+from .worktree_info import WorktreeInfo as WorktreeInfo
+from .worktree_list import WorktreeList as WorktreeList
+from .config_command import ConfigCommand as ConfigCommand
+from .config_warming import ConfigWarming as ConfigWarming
+from .credential_key import CredentialKey as CredentialKey
+from .file_diff_info import FileDiffInfo as FileDiffInfo
+from .integration_id import IntegrationID as IntegrationID
+from .model_settings import ModelSettings as ModelSettings
+from .prompt_mention import PromptMention as PromptMention
+from .reference_info import ReferenceInfo as ReferenceInfo
+from .session_revert import SessionRevert as SessionRevert
+from .session_status import SessionStatus as SessionStatus
+from .config_document import ConfigDocument as ConfigDocument
+from .config_provider import ConfigProvider as ConfigProvider
+from .config_worktree import ConfigWorktree as ConfigWorktree
+from .connection_info import ConnectionInfo as ConnectionInfo
+from .event_version10 import EventVersion10 as EventVersion10
+from .pairing_session import PairingSession as PairingSession
+from .permission_rule import PermissionRule as PermissionRule
+from .plugin_features import PluginFeatures as PluginFeatures
+from .sessions_cursor import SessionsCursor as SessionsCursor
+from .vcs_branch_list import VcsBranchList as VcsBranchList
+from .vcs_file_status import VcsFileStatus as VcsFileStatus
+from .voice_transport import VoiceTransport as VoiceTransport
+from .config_directory import ConfigDirectory as ConfigDirectory
+from .credential_entry import CredentialEntry as CredentialEntry
+from .credential_value import CredentialValue as CredentialValue
+from .instruction_hash import InstructionHash as InstructionHash
+from .integration_info import IntegrationInfo as IntegrationInfo
+from .mcp_local_config import McpLocalConfig as McpLocalConfig
+from .permission_reply import PermissionReply as PermissionReply
+from .project_commands import ProjectCommands as ProjectCommands
+from .provider_request import ProviderRequest as ProviderRequest
+from .pty_exited_event import PtyExitedEvent as PtyExitedEvent
+from .reference_source import ReferenceSource as ReferenceSource
+from .session_metadata import SessionMetadata as SessionMetadata
+from .token_usage_info import TokenUsageInfo as TokenUsageInfo
+from .config_lsp_server import ConfigLSPServer as ConfigLSPServer
+from .config_model_cost import ConfigModelCost as ConfigModelCost
+from .connection_status import ConnectionStatus as ConnectionStatus
+from .credential_o_auth import CredentialOAuth as CredentialOAuth
+from .file_system_entry import FileSystemEntry as FileSystemEntry
+from .file_system_write import FileSystemWrite as FileSystemWrite
+from .form_number_field import FormNumberField as FormNumberField
+from .form_string_field import FormStringField as FormStringField
+from .mcp_o_auth_config import McpOAuthConfig as McpOAuthConfig
+from .mcp_remote_config import McpRemoteConfig as McpRemoteConfig
+from .mcp_status_failed import McpStatusFailed as McpStatusFailed
+from .permission_effect import PermissionEffect as PermissionEffect
+from .permission_source import PermissionSource as PermissionSource
+from .provider_encoding import ProviderEncoding as ProviderEncoding
+from .provider_settings import ProviderSettings as ProviderSettings
+from .pty_created_event import PtyCreatedEvent as PtyCreatedEvent
+from .pty_deleted_event import PtyDeletedEvent as PtyDeletedEvent
+from .pty_updated_event import PtyUpdatedEvent as PtyUpdatedEvent
+from .sessions_response import SessionsResponse as SessionsResponse
+from .tool_file_content import ToolFileContent as ToolFileContent
+from .tool_text_content import ToolTextContent as ToolTextContent
+from .web_search_result import WebSearchResult as WebSearchResult
+from .form_boolean_field import FormBooleanField as FormBooleanField
+from .form_created_event import FormCreatedEvent as FormCreatedEvent
+from .form_integer_field import FormIntegerField as FormIntegerField
+from .form_replied_event import FormRepliedEvent as FormRepliedEvent
+from .integration_method import IntegrationMethod as IntegrationMethod
+from .mcp_status_pending import McpStatusPending as McpStatusPending
+from .model_cache_format import ModelCacheFormat as ModelCacheFormat
+from .model_capabilities import ModelCapabilities as ModelCapabilities
+from .permission_request import PermissionRequest as PermissionRequest
+from .permission_ruleset import PermissionRuleset as PermissionRuleset
+from .prompt_file_source import PromptFileSource as PromptFileSource
+from .provider_transport import ProviderTransport as ProviderTransport
+from .session_idle_event import SessionIdleEvent as SessionIdleEvent
+from .session_inbox_info import SessionInboxInfo as SessionInboxInfo
+from .session_inbox_item import SessionInboxItem as SessionInboxItem
+from .session_inbox_move import SessionInboxMove as SessionInboxMove
+from .session_inbox_user import SessionInboxUser as SessionInboxUser
+from .session_message_id import SessionMessageID as SessionMessageID
+from .session_stats_info import SessionStatsInfo as SessionStatsInfo
+from .shell_exited_event import ShellExitedEvent as ShellExitedEvent
+from .worktree_directory import WorktreeDirectory as WorktreeDirectory
+from .agent_updated_event import AgentUpdatedEvent as AgentUpdatedEvent
+from .config_plugin_entry import ConfigPluginEntry as ConfigPluginEntry
+from .config_shell_option import ConfigShellOption as ConfigShellOption
+from .connection_env_info import ConnectionEnvInfo as ConnectionEnvInfo
+from .credential_external import CredentialExternal as CredentialExternal
+from .form_external_field import FormExternalField as FormExternalField
+from .integration_attempt import IntegrationAttempt as IntegrationAttempt
+from .location_public_ref import LocationPublicRef as LocationPublicRef
+from .mcp_status_disabled import McpStatusDisabled as McpStatusDisabled
+from .model_compatibility import ModelCompatibility as ModelCompatibility
+from .model_updated_event import ModelUpdatedEvent as ModelUpdatedEvent
+from .permission_saved_id import PermissionSavedID as PermissionSavedID
+from .persistent_pty_info import PersistentPtyInfo as PersistentPtyInfo
+from .provider_compaction import ProviderCompaction as ProviderCompaction
+from .session_moved_event import SessionMovedEvent as SessionMovedEvent
+from .session_stats_tools import SessionStatsTools as SessionStatsTools
+from .shell_created_event import ShellCreatedEvent as ShellCreatedEvent
+from .shell_deleted_event import ShellDeletedEvent as ShellDeletedEvent
+from .skill_updated_event import SkillUpdatedEvent as SkillUpdatedEvent
+from .web_search_provider import WebSearchProvider as WebSearchProvider
+from .web_search_response import WebSearchResponse as WebSearchResponse
+from .config_reference_git import ConfigReferenceGit as ConfigReferenceGit
+from .config_updated_event import ConfigUpdatedEvent as ConfigUpdatedEvent
+from .form_cancelled_event import FormCancelledEvent as FormCancelledEvent
+from .location_public_info import LocationPublicInfo as LocationPublicInfo
+from .mcp_resource_catalog import McpResourceCatalog as McpResourceCatalog
+from .mcp_status_connected import McpStatusConnected as McpStatusConnected
+from .plugin_updated_event import PluginUpdatedEvent as PluginUpdatedEvent
+from .reference_git_source import ReferenceGitSource as ReferenceGitSource
+from .session_forked_event import SessionForkedEvent as SessionForkedEvent
+from .session_message_idle import SessionMessageIdle as SessionMessageIdle
+from .session_message_info import SessionMessageInfo as SessionMessageInfo
+from .session_message_user import SessionMessageUser as SessionMessageUser
+from .session_viewed_event import SessionViewedEvent as SessionViewedEvent
+from .tui_toast_show_event import TuiToastShowEvent as TuiToastShowEvent
+from .command_updated_event import CommandUpdatedEvent as CommandUpdatedEvent
+from .config_model_settings import ConfigModelSettings as ConfigModelSettings
+from .instruction_entry_key import InstructionEntryKey as InstructionEntryKey
+from .integration_method_id import IntegrationMethodID as IntegrationMethodID
+from .mcp_resource_template import McpResourceTemplate as McpResourceTemplate
+from .mcp_status_needs_auth import McpStatusNeedsAuth as McpStatusNeedsAuth
+from .model_reasoning_field import ModelReasoningField as ModelReasoningField
+from .permission_saved_info import PermissionSavedInfo as PermissionSavedInfo
+from .project_updated_event import ProjectUpdatedEvent as ProjectUpdatedEvent
+from .session_created_event import SessionCreatedEvent as SessionCreatedEvent
+from .session_deleted_event import SessionDeletedEvent as SessionDeletedEvent
+from .session_fork_boundary import SessionForkBoundary as SessionForkBoundary
+from .session_message_shell import SessionMessageShell as SessionMessageShell
+from .session_message_skill import SessionMessageSkill as SessionMessageSkill
+from .session_renamed_event import SessionRenamedEvent as SessionRenamedEvent
+from .session_transfer_data import SessionTransferData as SessionTransferData
+from .voice_transport_input import VoiceTransportInput as VoiceTransportInput
+from .config_formatter_entry import ConfigFormatterEntry as ConfigFormatterEntry
+from .config_model_reasoning import ConfigModelReasoning as ConfigModelReasoning
+from .config_reference_local import ConfigReferenceLocal as ConfigReferenceLocal
+from .config_web_search_info import ConfigWebSearchInfo as ConfigWebSearchInfo
+from .form_multiselect_field import FormMultiselectField as FormMultiselectField
+from .generate_text_response import GenerateTextResponse as GenerateTextResponse
+from .instruction_entry_info import InstructionEntryInfo as InstructionEntryInfo
+from .integration_attempt_id import IntegrationAttemptID as IntegrationAttemptID
+from .integration_env_method import IntegrationEnvMethod as IntegrationEnvMethod
+from .integration_key_method import IntegrationKeyMethod as IntegrationKeyMethod
+from .model_max_tokens_field import ModelMaxTokensField as ModelMaxTokensField
+from .permission_asked_event import PermissionAskedEvent as PermissionAskedEvent
+from .persistent_pty_handoff import PersistentPtyHandoff as PersistentPtyHandoff
+from .prompt_file_attachment import PromptFileAttachment as PromptFileAttachment
+from .provider_compatibility import ProviderCompatibility as ProviderCompatibility
+from .provider_updated_event import ProviderUpdatedEvent as ProviderUpdatedEvent
+from .reference_local_source import ReferenceLocalSource as ReferenceLocalSource
+from .server_connected_event import ServerConnectedEvent as ServerConnectedEvent
+from .session_archived_event import SessionArchivedEvent as SessionArchivedEvent
+from .session_inbox_delivery import SessionInboxDelivery as SessionInboxDelivery
+from .session_message_native import SessionMessageNative as SessionMessageNative
+from .session_message_system import SessionMessageSystem as SessionMessageSystem
+from .session_stats_activity import SessionStatsActivity as SessionStatsActivity
+from .session_status_updated import SessionStatusUpdated as SessionStatusUpdated
+from .voice_session_response import VoiceSessionResponse as VoiceSessionResponse
+from .worktree_updated_event import WorktreeUpdatedEvent as WorktreeUpdatedEvent
+from .location_shutdown_event import LocationShutdownEvent as LocationShutdownEvent
+from .persistent_pty_snapshot import PersistentPtySnapshot as PersistentPtySnapshot
+from .prompt_agent_attachment import PromptAgentAttachment as PromptAgentAttachment
+from .prompt_skill_attachment import PromptSkillAttachment as PromptSkillAttachment
+from .reference_updated_event import ReferenceUpdatedEvent as ReferenceUpdatedEvent
+from .session_inbox_synthetic import SessionInboxSynthetic as SessionInboxSynthetic
+from .session_message_native1 import SessionMessageNative1 as SessionMessageNative1
+from .session_message_native2 import SessionMessageNative2 as SessionMessageNative2
+from .session_message_native3 import SessionMessageNative3 as SessionMessageNative3
+from .session_message_native4 import SessionMessageNative4 as SessionMessageNative4
+from .session_message_native5 import SessionMessageNative5 as SessionMessageNative5
+from .session_message_native6 import SessionMessageNative6 as SessionMessageNative6
+from .session_message_native7 import SessionMessageNative7 as SessionMessageNative7
+from .session_message_native8 import SessionMessageNative8 as SessionMessageNative8
+from .session_message_native9 import SessionMessageNative9 as SessionMessageNative9
+from .session_synthetic_event import SessionSyntheticEvent as SessionSyntheticEvent
+from .tui_prompt_append_event import TuiPromptAppendEvent as TuiPromptAppendEvent
+from .voice_web_rtc_transport import VoiceWebRTCTransport as VoiceWebRTCTransport
+from .websearch_updated_event import WebsearchUpdatedEvent as WebsearchUpdatedEvent
+from .worktree_resolved_event import WorktreeResolvedEvent as WorktreeResolvedEvent
+from .config_provider_settings import ConfigProviderSettings as ConfigProviderSettings
+from .credential_updated_event import CredentialUpdatedEvent as CredentialUpdatedEvent
+from .filesystem_changed_event import FilesystemChangedEvent as FilesystemChangedEvent
+from .mcp_status_changed_event import McpStatusChangedEvent as McpStatusChangedEvent
+from .permission_replied_event import PermissionRepliedEvent as PermissionRepliedEvent
+from .pty_ticket_connect_token import PtyTicketConnectToken as PtyTicketConnectToken
+from .session_inbox_compaction import SessionInboxCompaction as SessionInboxCompaction
+from .session_message_native10 import SessionMessageNative10 as SessionMessageNative10
+from .session_message_native11 import SessionMessageNative11 as SessionMessageNative11
+from .session_message_native12 import SessionMessageNative12 as SessionMessageNative12
+from .session_stats_tool_usage import SessionStatsToolUsage as SessionStatsToolUsage
+from .session_step_ended_event import SessionStepEndedEvent as SessionStepEndedEvent
+from .session_structured_error import SessionStructuredError as SessionStructuredError
+from .session_text_delta_event import SessionTextDeltaEvent as SessionTextDeltaEvent
+from .session_text_ended_event import SessionTextEndedEvent as SessionTextEndedEvent
+from .session_unarchived_event import SessionUnarchivedEvent as SessionUnarchivedEvent
+from .tui_session_select_event import TuiSessionSelectEvent as TuiSessionSelectEvent
+from .vcs_branch_updated_event import VcsBranchUpdatedEvent as VcsBranchUpdatedEvent
+from .config_model_capabilities import ConfigModelCapabilities as ConfigModelCapabilities
+from .credential_switched_event import CredentialSwitchedEvent as CredentialSwitchedEvent
+from .integration_o_auth_method import IntegrationOAuthMethod as IntegrationOAuthMethod
+from .integration_updated_event import IntegrationUpdatedEvent as IntegrationUpdatedEvent
+from .persistent_pty_read_lines import PersistentPtyReadLines as PersistentPtyReadLines
+from .session_generate_response import SessionGenerateResponse as SessionGenerateResponse
+from .session_message_assistant import SessionMessageAssistant as SessionMessageAssistant
+from .session_message_synthetic import SessionMessageSynthetic as SessionMessageSynthetic
+from .session_messages_response import SessionMessagesResponse as SessionMessagesResponse
+from .session_permissions_event import SessionPermissionsEvent as SessionPermissionsEvent
+from .session_shell_ended_event import SessionShellEndedEvent as SessionShellEndedEvent
+from .session_stats_model_usage import SessionStatsModelUsage as SessionStatsModelUsage
+from .session_stats_tool_totals import SessionStatsToolTotals as SessionStatsToolTotals
+from .session_step_failed_event import SessionStepFailedEvent as SessionStepFailedEvent
+from .session_tool_called_event import SessionToolCalledEvent as SessionToolCalledEvent
+from .session_tool_failed_event import SessionToolFailedEvent as SessionToolFailedEvent
+from .tui_command_execute_event import TuiCommandExecuteEvent as TuiCommandExecuteEvent
+from .connection_credential_info import ConnectionCredentialInfo as ConnectionCredentialInfo
+from .installation_updated_event import InstallationUpdatedEvent as InstallationUpdatedEvent
+from .instruction_entry_snapshot import InstructionEntrySnapshot as InstructionEntrySnapshot
+from .integration_attempt_status import IntegrationAttemptStatus as IntegrationAttemptStatus
+from .integration_command_method import IntegrationCommandMethod as IntegrationCommandMethod
+from .models_dev_refreshed_event import ModelsDevRefreshedEvent as ModelsDevRefreshedEvent
+from .persistent_pty_added_event import PersistentPtyAddedEvent as PersistentPtyAddedEvent
+from .persistent_pty_read_result import PersistentPtyReadResult as PersistentPtyReadResult
+from .session_inbox_move_payload import SessionInboxMovePayload as SessionInboxMovePayload
+from .session_inbox_user_payload import SessionInboxUserPayload as SessionInboxUserPayload
+from .session_interrupt_response import SessionInterruptResponse as SessionInterruptResponse
+from .session_message_compaction import SessionMessageCompaction as SessionMessageCompaction
+from .session_step_started_event import SessionStepStartedEvent as SessionStepStartedEvent
+from .session_text_started_event import SessionTextStartedEvent as SessionTextStartedEvent
+from .session_tool_success_event import SessionToolSuccessEvent as SessionToolSuccessEvent
+from .voice_web_socket_transport import VoiceWebSocketTransport as VoiceWebSocketTransport
+from .integration_command_attempt import IntegrationCommandAttempt as IntegrationCommandAttempt
+from .integration_external_method import IntegrationExternalMethod as IntegrationExternalMethod
+from .mcp_resources_changed_event import McpResourcesChangedEvent as McpResourcesChangedEvent
+from .session_inbox_move_payload1 import SessionInboxMovePayload1 as SessionInboxMovePayload1
+from .session_revert_staged_event import SessionRevertStagedEvent as SessionRevertStagedEvent
+from .session_shell_started_event import SessionShellStartedEvent as SessionShellStartedEvent
+from .session_step_streamed_event import SessionStepStreamedEvent as SessionStepStreamedEvent
+from .session_tool_progress_event import SessionToolProgressEvent as SessionToolProgressEvent
+from .session_usage_updated_event import SessionUsageUpdatedEvent as SessionUsageUpdatedEvent
+from .money_usd_per_million_tokens import MoneyUSDPerMillionTokens as MoneyUSDPerMillionTokens
+from .persistent_pty_removed_event import PersistentPtyRemovedEvent as PersistentPtyRemovedEvent
+from .prompt_input_file_attachment import PromptInputFileAttachment as PromptInputFileAttachment
+from .session_agent_selected_event import SessionAgentSelectedEvent as SessionAgentSelectedEvent
+from .session_inbox_enqueued_event import SessionInboxEnqueuedEvent as SessionInboxEnqueuedEvent
+from .session_model_selected_event import SessionModelSelectedEvent as SessionModelSelectedEvent
+from .session_revert_cleared_event import SessionRevertClearedEvent as SessionRevertClearedEvent
+from .prompt_input_skill_attachment import PromptInputSkillAttachment as PromptInputSkillAttachment
+from .session_inbox_cancelled_event import SessionInboxCancelledEvent as SessionInboxCancelledEvent
+from .session_inbox_delivered_event import SessionInboxDeliveredEvent as SessionInboxDeliveredEvent
+from .session_reasoning_delta_event import SessionReasoningDeltaEvent as SessionReasoningDeltaEvent
+from .session_reasoning_ended_event import SessionReasoningEndedEvent as SessionReasoningEndedEvent
+from .session_retry_scheduled_event import SessionRetryScheduledEvent as SessionRetryScheduledEvent
+from .session_skill_activated_event import SessionSkillActivatedEvent as SessionSkillActivatedEvent
+from .voice_web_rtc_transport_input import VoiceWebRTCTransportInput as VoiceWebRTCTransportInput
+from .session_compaction_delta_event import SessionCompactionDeltaEvent as SessionCompactionDeltaEvent
+from .session_compaction_ended_event import SessionCompactionEndedEvent as SessionCompactionEndedEvent
+from .session_execution_failed_event import SessionExecutionFailedEvent as SessionExecutionFailedEvent
+from .session_message_agent_selected import SessionMessageAgentSelected as SessionMessageAgentSelected
+from .session_message_assistant_text import SessionMessageAssistantText as SessionMessageAssistantText
+from .session_message_assistant_tool import SessionMessageAssistantTool as SessionMessageAssistantTool
+from .session_message_model_selected import SessionMessageModelSelected as SessionMessageModelSelected
+from .session_metadata_updated_event import SessionMetadataUpdatedEvent as SessionMetadataUpdatedEvent
+from .session_revert_committed_event import SessionRevertCommittedEvent as SessionRevertCommittedEvent
+from .session_tool_input_delta_event import SessionToolInputDeltaEvent as SessionToolInputDeltaEvent
+from .session_tool_input_ended_event import SessionToolInputEndedEvent as SessionToolInputEndedEvent
+from .session_compaction_failed_event import SessionCompactionFailedEvent as SessionCompactionFailedEvent
+from .session_execution_started_event import SessionExecutionStartedEvent as SessionExecutionStartedEvent
+from .session_inbox_synthetic_payload import SessionInboxSyntheticPayload as SessionInboxSyntheticPayload
+from .session_message_assistant_retry import SessionMessageAssistantRetry as SessionMessageAssistantRetry
+from .session_reasoning_started_event import SessionReasoningStartedEvent as SessionReasoningStartedEvent
+from .session_compaction_started_event import SessionCompactionStartedEvent as SessionCompactionStartedEvent
+from .session_inbox_compaction_payload import SessionInboxCompactionPayload as SessionInboxCompactionPayload
+from .session_message_tool_state_error import SessionMessageToolStateError as SessionMessageToolStateError
+from .session_tool_input_started_event import SessionToolInputStartedEvent as SessionToolInputStartedEvent
+from .voice_web_socket_transport_input import VoiceWebSocketTransportInput as VoiceWebSocketTransportInput
+from .session_execution_succeeded_event import SessionExecutionSucceededEvent as SessionExecutionSucceededEvent
+from .session_message_compaction_failed import SessionMessageCompactionFailed as SessionMessageCompactionFailed
+from .session_message_location_switched import SessionMessageLocationSwitched as SessionMessageLocationSwitched
+from .integration_command_attempt_status import IntegrationCommandAttemptStatus as IntegrationCommandAttemptStatus
+from .session_instructions_updated_event import SessionInstructionsUpdatedEvent as SessionInstructionsUpdatedEvent
+from .session_message_compaction_running import SessionMessageCompactionRunning as SessionMessageCompactionRunning
+from .session_message_tool_state_running import SessionMessageToolStateRunning as SessionMessageToolStateRunning
+from .installation_update_available_event import InstallationUpdateAvailableEvent as InstallationUpdateAvailableEvent
+from .session_execution_interrupted_event import SessionExecutionInterruptedEvent as SessionExecutionInterruptedEvent
+from .session_message_assistant_reasoning import SessionMessageAssistantReasoning as SessionMessageAssistantReasoning
+from .session_inbox_delivery_changed_event import SessionInboxDeliveryChangedEvent as SessionInboxDeliveryChangedEvent
+from .session_message_compaction_completed import SessionMessageCompactionCompleted as SessionMessageCompactionCompleted
+from .session_message_tool_state_completed import SessionMessageToolStateCompleted as SessionMessageToolStateCompleted
+from .session_message_tool_state_streaming import SessionMessageToolStateStreaming as SessionMessageToolStateStreaming
diff --git a/src/opencode/types/shared/absolute_path.py b/src/opencode/types/shared/absolute_path.py
new file mode 100644
index 0000000..3d7844e
--- /dev/null
+++ b/src/opencode/types/shared/absolute_path.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["AbsolutePath"]
+
+AbsolutePath: TypeAlias = str
diff --git a/src/opencode/types/shared/agent_color.py b/src/opencode/types/shared/agent_color.py
new file mode 100644
index 0000000..d84a2e1
--- /dev/null
+++ b/src/opencode/types/shared/agent_color.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["AgentColor"]
+
+AgentColor: TypeAlias = str
diff --git a/src/opencode/types/shared/agent_id.py b/src/opencode/types/shared/agent_id.py
new file mode 100644
index 0000000..5c92724
--- /dev/null
+++ b/src/opencode/types/shared/agent_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["AgentID"]
+
+AgentID: TypeAlias = str
diff --git a/src/opencode/types/shared/agent_info.py b/src/opencode/types/shared/agent_info.py
new file mode 100644
index 0000000..27c22f7
--- /dev/null
+++ b/src/opencode/types/shared/agent_info.py
@@ -0,0 +1,51 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .agent_id import AgentID
+from ..._models import BaseModel
+from .model_ref import ModelRef
+from .agent_color import AgentColor
+from .provider_request import ProviderRequest
+from .permission_ruleset import PermissionRuleset
+
+__all__ = ["AgentInfo"]
+
+
+class AgentInfo(BaseModel):
+ id: AgentID
+
+ hidden: bool
+
+ mode: Literal["subagent", "primary", "all"]
+
+ name: str
+
+ permissions: PermissionRuleset
+
+ request: ProviderRequest
+
+ color: Optional[AgentColor] = None
+
+ description: Optional[str] = None
+
+ model: Optional[ModelRef] = None
+
+ steps: Optional[int] = None
+
+ system: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/agent_updated_event.py b/src/opencode/types/shared/agent_updated_event.py
new file mode 100644
index 0000000..d62eb30
--- /dev/null
+++ b/src/opencode/types/shared/agent_updated_event.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["AgentUpdatedEvent"]
+
+
+class AgentUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: object
+
+ type: Literal["agent.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/command_info.py b/src/opencode/types/shared/command_info.py
new file mode 100644
index 0000000..ce71e10
--- /dev/null
+++ b/src/opencode/types/shared/command_info.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["CommandInfo"]
+
+
+class CommandInfo(BaseModel):
+ name: str
+
+ description: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/command_updated_event.py b/src/opencode/types/shared/command_updated_event.py
new file mode 100644
index 0000000..209a894
--- /dev/null
+++ b/src/opencode/types/shared/command_updated_event.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["CommandUpdatedEvent"]
+
+
+class CommandUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: object
+
+ type: Literal["command.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_agent.py b/src/opencode/types/shared/config_agent.py
new file mode 100644
index 0000000..6a66bd7
--- /dev/null
+++ b/src/opencode/types/shared/config_agent.py
@@ -0,0 +1,83 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .permission_ruleset import PermissionRuleset
+
+__all__ = ["ConfigAgent", "Model", "Request"]
+
+
+class Model(BaseModel):
+ model: str
+
+ provider_id: str = FieldInfo(alias="providerID")
+
+ variant: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Request(BaseModel):
+ body: Optional[Dict[str, object]] = None
+
+ headers: Optional[Dict[str, str]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class ConfigAgent(BaseModel):
+ color: Optional[str] = None
+
+ description: Optional[str] = None
+
+ disabled: Optional[bool] = None
+
+ hidden: Optional[bool] = None
+
+ mode: Optional[Literal["subagent", "primary", "all"]] = None
+
+ model: Union[str, Model, None] = None
+
+ permissions: Optional[PermissionRuleset] = None
+
+ request: Optional[Request] = None
+
+ steps: Optional[int] = None
+
+ system: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_command.py b/src/opencode/types/shared/config_command.py
new file mode 100644
index 0000000..143597a
--- /dev/null
+++ b/src/opencode/types/shared/config_command.py
@@ -0,0 +1,56 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ConfigCommand", "Model"]
+
+
+class Model(BaseModel):
+ model: str
+
+ provider_id: str = FieldInfo(alias="providerID")
+
+ variant: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class ConfigCommand(BaseModel):
+ template: str
+
+ agent: Optional[str] = None
+
+ description: Optional[str] = None
+
+ model: Union[str, Model, None] = None
+
+ subagent: Optional[bool] = None
+
+ subtask: Optional[bool] = None
+ """Deprecated alias for subagent."""
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_directory.py b/src/opencode/types/shared/config_directory.py
new file mode 100644
index 0000000..2840e90
--- /dev/null
+++ b/src/opencode/types/shared/config_directory.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .absolute_path import AbsolutePath
+
+__all__ = ["ConfigDirectory"]
+
+
+class ConfigDirectory(BaseModel):
+ path: AbsolutePath
+
+ type: Literal["directory"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_document.py b/src/opencode/types/shared/config_document.py
new file mode 100644
index 0000000..11a89c0
--- /dev/null
+++ b/src/opencode/types/shared/config_document.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .config_info import ConfigInfo
+from .absolute_path import AbsolutePath
+
+__all__ = ["ConfigDocument"]
+
+
+class ConfigDocument(BaseModel):
+ info: ConfigInfo
+
+ type: Literal["document"]
+
+ path: Optional[AbsolutePath] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_entry.py b/src/opencode/types/shared/config_entry.py
new file mode 100644
index 0000000..c62dfd4
--- /dev/null
+++ b/src/opencode/types/shared/config_entry.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import TypeAlias
+
+from .config_document import ConfigDocument
+from .config_directory import ConfigDirectory
+
+__all__ = ["ConfigEntry"]
+
+ConfigEntry: TypeAlias = Union[ConfigDocument, ConfigDirectory]
diff --git a/src/opencode/types/shared/config_formatter_entry.py b/src/opencode/types/shared/config_formatter_entry.py
new file mode 100644
index 0000000..ce72785
--- /dev/null
+++ b/src/opencode/types/shared/config_formatter_entry.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ConfigFormatterEntry"]
+
+
+class ConfigFormatterEntry(BaseModel):
+ command: Optional[List[str]] = None
+
+ disabled: Optional[bool] = None
+
+ environment: Optional[Dict[str, str]] = None
+
+ extensions: Optional[List[str]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_info.py b/src/opencode/types/shared/config_info.py
new file mode 100644
index 0000000..72fde97
--- /dev/null
+++ b/src/opencode/types/shared/config_info.py
@@ -0,0 +1,392 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Union, Optional
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .config_agent import ConfigAgent
+from .config_command import ConfigCommand
+from .config_warming import ConfigWarming
+from .config_provider import ConfigProvider
+from .config_worktree import ConfigWorktree
+from .mcp_local_config import McpLocalConfig
+from .config_lsp_server import ConfigLSPServer
+from .mcp_remote_config import McpRemoteConfig
+from .permission_ruleset import PermissionRuleset
+from .config_plugin_entry import ConfigPluginEntry
+from .config_reference_git import ConfigReferenceGit
+from .config_formatter_entry import ConfigFormatterEntry
+from .config_reference_local import ConfigReferenceLocal
+from .config_web_search_info import ConfigWebSearchInfo
+
+__all__ = [
+ "ConfigInfo",
+ "Compaction",
+ "CompactionKeep",
+ "Enterprise",
+ "Experimental",
+ "ExperimentalPolicy",
+ "Lsp",
+ "Mcp",
+ "McpServers",
+ "McpTimeout",
+ "Media",
+ "MediaImage",
+ "Model",
+ "Plugin",
+ "Policy",
+ "References",
+ "ToolOutput",
+ "Warming",
+ "Watcher",
+ "Websearch",
+]
+
+
+class CompactionKeep(BaseModel):
+ tokens: Optional[int] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Compaction(BaseModel):
+ auto: Optional[bool] = None
+
+ buffer: Optional[int] = None
+
+ keep: Optional[CompactionKeep] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Enterprise(BaseModel):
+ url: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class ExperimentalPolicy(BaseModel):
+ action: Literal["provider.use", "tool.use", "integration.use"]
+
+ effect: Literal["allow", "deny"]
+
+ resource: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Experimental(BaseModel):
+ location_idle_timeout: Union[Literal[False], str, None] = None
+
+ policies: Optional[List[ExperimentalPolicy]] = None
+
+ portable_shell_scanner: Optional[bool] = None
+
+ subagent_depth: Optional[int] = None
+
+ todo: Optional[bool] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Lsp(BaseModel):
+ disabled: Literal[True]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+McpServers: TypeAlias = Union[McpLocalConfig, McpRemoteConfig]
+
+
+class McpTimeout(BaseModel):
+ catalog: Optional[int] = None
+
+ execution: Optional[int] = None
+
+ startup: Optional[int] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Mcp(BaseModel):
+ servers: Optional[Dict[str, McpServers]] = None
+
+ timeout: Optional[McpTimeout] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class MediaImage(BaseModel):
+ auto_resize: Optional[bool] = None
+
+ max_base64_bytes: Optional[int] = None
+
+ max_height: Optional[int] = None
+
+ max_width: Optional[int] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Media(BaseModel):
+ image: Optional[MediaImage] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Model(BaseModel):
+ model: str
+
+ provider_id: str = FieldInfo(alias="providerID")
+
+ variant: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+Plugin: TypeAlias = Union[str, ConfigPluginEntry]
+
+
+class Policy(BaseModel):
+ action: Literal["provider.use", "tool.use", "integration.use"]
+
+ effect: Literal["allow", "deny"]
+
+ resource: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+References: TypeAlias = Union[str, ConfigReferenceGit, ConfigReferenceLocal]
+
+
+class ToolOutput(BaseModel):
+ max_bytes: Optional[int] = None
+
+ max_lines: Optional[int] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+Warming: TypeAlias = Union[bool, ConfigWarming]
+
+
+class Watcher(BaseModel):
+ ignore: Optional[List[str]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+Websearch: TypeAlias = Union[Literal[False], ConfigWebSearchInfo]
+
+
+class ConfigInfo(BaseModel):
+ agents: Optional[Dict[str, ConfigAgent]] = None
+
+ commands: Optional[Dict[str, ConfigCommand]] = None
+
+ compaction: Optional[Compaction] = None
+
+ default_agent: Optional[str] = None
+
+ enterprise: Optional[Enterprise] = None
+
+ experimental: Optional[Experimental] = None
+
+ formatter: Union[bool, Dict[str, ConfigFormatterEntry], None] = None
+
+ instructions: Optional[List[str]] = None
+
+ lsp: Union[bool, Dict[str, Union[Lsp, ConfigLSPServer]], None] = None
+
+ mcp: Optional[Mcp] = None
+
+ media: Optional[Media] = None
+
+ model: Union[str, Model, None] = None
+
+ permissions: Optional[PermissionRuleset] = None
+
+ plugins: Optional[List[Plugin]] = None
+
+ policies: Optional[List[Policy]] = None
+
+ providers: Optional[Dict[str, ConfigProvider]] = None
+
+ references: Optional[Dict[str, References]] = None
+
+ share: Optional[Literal["manual", "auto", "disabled"]] = None
+
+ shell: Optional[str] = None
+
+ skills: Optional[List[str]] = None
+
+ snapshots: Optional[bool] = None
+
+ tool_output: Optional[ToolOutput] = None
+
+ update: Optional[Literal["disable", "notify", "auto"]] = None
+
+ username: Optional[str] = None
+
+ warming: Optional[Warming] = None
+
+ watcher: Optional[Watcher] = None
+
+ websearch: Optional[Websearch] = None
+
+ worktree: Optional[ConfigWorktree] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_lsp_server.py b/src/opencode/types/shared/config_lsp_server.py
new file mode 100644
index 0000000..a61eb79
--- /dev/null
+++ b/src/opencode/types/shared/config_lsp_server.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ConfigLSPServer"]
+
+
+class ConfigLSPServer(BaseModel):
+ command: List[str]
+
+ disabled: Optional[bool] = None
+
+ env: Optional[Dict[str, str]] = None
+
+ extensions: Optional[List[str]] = None
+
+ initialization: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_model.py b/src/opencode/types/shared/config_model.py
new file mode 100644
index 0000000..dbd385c
--- /dev/null
+++ b/src/opencode/types/shared/config_model.py
@@ -0,0 +1,101 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Union, Optional
+from typing_extensions import TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .config_model_cost import ConfigModelCost
+from .model_compatibility import ModelCompatibility
+from .config_model_settings import ConfigModelSettings
+from .config_model_capabilities import ConfigModelCapabilities
+
+__all__ = ["ConfigModel", "Cost", "Limit", "Variant"]
+
+Cost: TypeAlias = Union[ConfigModelCost, List[ConfigModelCost]]
+
+
+class Limit(BaseModel):
+ context: Optional[int] = None
+
+ input: Optional[int] = None
+
+ output: Optional[int] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Variant(BaseModel):
+ id: str
+
+ body: Optional[Dict[str, object]] = None
+
+ headers: Optional[Dict[str, str]] = None
+
+ settings: Optional[ConfigModelSettings] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class ConfigModel(BaseModel):
+ body: Optional[Dict[str, object]] = None
+
+ capabilities: Optional[ConfigModelCapabilities] = None
+
+ compatibility: Optional[ModelCompatibility] = None
+
+ cost: Optional[Cost] = None
+
+ disabled: Optional[bool] = None
+
+ family: Optional[str] = None
+
+ headers: Optional[Dict[str, str]] = None
+
+ limit: Optional[Limit] = None
+
+ api_model_id: Optional[str] = FieldInfo(alias="modelID", default=None)
+
+ name: Optional[str] = None
+
+ package: Optional[str] = None
+
+ release_date: Optional[str] = FieldInfo(alias="releaseDate", default=None)
+ """Release date as YYYY-MM-DD or YYYY-MM"""
+
+ settings: Optional[ConfigModelSettings] = None
+
+ variants: Optional[List[Variant]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_model_capabilities.py b/src/opencode/types/shared/config_model_capabilities.py
new file mode 100644
index 0000000..e81eede
--- /dev/null
+++ b/src/opencode/types/shared/config_model_capabilities.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .config_model_reasoning import ConfigModelReasoning
+
+__all__ = ["ConfigModelCapabilities"]
+
+
+class ConfigModelCapabilities(BaseModel):
+ input: Optional[List[str]] = None
+
+ output: Optional[List[str]] = None
+
+ reasoning: Optional[ConfigModelReasoning] = None
+
+ temperature: Optional[bool] = None
+
+ tools: Optional[bool] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_model_cost.py b/src/opencode/types/shared/config_model_cost.py
new file mode 100644
index 0000000..38e493a
--- /dev/null
+++ b/src/opencode/types/shared/config_model_cost.py
@@ -0,0 +1,69 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .money_usd_per_million_tokens import MoneyUSDPerMillionTokens
+
+__all__ = ["ConfigModelCost", "Cache", "Tier"]
+
+
+class Cache(BaseModel):
+ read: Optional[MoneyUSDPerMillionTokens] = None
+
+ write: Optional[MoneyUSDPerMillionTokens] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Tier(BaseModel):
+ size: int
+
+ type: Literal["context"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class ConfigModelCost(BaseModel):
+ input: MoneyUSDPerMillionTokens
+
+ output: MoneyUSDPerMillionTokens
+
+ cache: Optional[Cache] = None
+
+ tier: Optional[Tier] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_model_reasoning.py b/src/opencode/types/shared/config_model_reasoning.py
new file mode 100644
index 0000000..97afd6a
--- /dev/null
+++ b/src/opencode/types/shared/config_model_reasoning.py
@@ -0,0 +1,74 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Union, Optional
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = [
+ "ConfigModelReasoning",
+ "ConfigModelReasoningVariant0",
+ "ConfigModelReasoningVariant1",
+ "ConfigModelReasoningVariant1Budget",
+]
+
+
+class ConfigModelReasoningVariant0(BaseModel):
+ supported: Literal[False]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class ConfigModelReasoningVariant1Budget(BaseModel):
+ max: Optional[int] = None
+
+ min: Optional[int] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class ConfigModelReasoningVariant1(BaseModel):
+ supported: Literal[True]
+
+ budget: Optional[ConfigModelReasoningVariant1Budget] = None
+
+ effort: Optional[List[str]] = None
+
+ toggle: Optional[bool] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+ConfigModelReasoning: TypeAlias = Union[ConfigModelReasoningVariant0, ConfigModelReasoningVariant1]
diff --git a/src/opencode/types/shared/config_model_settings.py b/src/opencode/types/shared/config_model_settings.py
new file mode 100644
index 0000000..d5896df
--- /dev/null
+++ b/src/opencode/types/shared/config_model_settings.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .provider_encoding import ProviderEncoding
+from .provider_compaction import ProviderCompaction
+
+__all__ = ["ConfigModelSettings"]
+
+
+class ConfigModelSettings(BaseModel):
+ compaction: Optional[ProviderCompaction] = None
+
+ encoding: Optional[ProviderEncoding] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_plugin_entry.py b/src/opencode/types/shared/config_plugin_entry.py
new file mode 100644
index 0000000..d476f33
--- /dev/null
+++ b/src/opencode/types/shared/config_plugin_entry.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ConfigPluginEntry"]
+
+
+class ConfigPluginEntry(BaseModel):
+ package: str
+
+ options: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_provider.py b/src/opencode/types/shared/config_provider.py
new file mode 100644
index 0000000..241719b
--- /dev/null
+++ b/src/opencode/types/shared/config_provider.py
@@ -0,0 +1,45 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .provider_id import ProviderID
+from .config_model import ConfigModel
+from .provider_compatibility import ProviderCompatibility
+from .config_provider_settings import ConfigProviderSettings
+
+__all__ = ["ConfigProvider"]
+
+
+class ConfigProvider(BaseModel):
+ body: Optional[Dict[str, object]] = None
+
+ canonical: Optional[ProviderID] = None
+
+ compatibility: Optional[ProviderCompatibility] = None
+
+ env: Optional[List[str]] = None
+
+ headers: Optional[Dict[str, str]] = None
+
+ models: Optional[Dict[str, ConfigModel]] = None
+
+ name: Optional[str] = None
+
+ package: Optional[str] = None
+
+ settings: Optional[ConfigProviderSettings] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_provider_settings.py b/src/opencode/types/shared/config_provider_settings.py
new file mode 100644
index 0000000..688971e
--- /dev/null
+++ b/src/opencode/types/shared/config_provider_settings.py
@@ -0,0 +1,39 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .provider_encoding import ProviderEncoding
+from .provider_transport import ProviderTransport
+from .provider_compaction import ProviderCompaction
+
+__all__ = ["ConfigProviderSettings"]
+
+
+class ConfigProviderSettings(BaseModel):
+ chunk_timeout: Union[Literal[False], float, None] = FieldInfo(alias="chunkTimeout", default=None)
+
+ compaction: Optional[ProviderCompaction] = None
+
+ encoding: Optional[ProviderEncoding] = None
+
+ header_timeout: Union[Literal[False], float, None] = FieldInfo(alias="headerTimeout", default=None)
+
+ timeout: Union[Literal[False], float, None] = None
+
+ transport: Optional[ProviderTransport] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_reference_git.py b/src/opencode/types/shared/config_reference_git.py
new file mode 100644
index 0000000..d575491
--- /dev/null
+++ b/src/opencode/types/shared/config_reference_git.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ConfigReferenceGit"]
+
+
+class ConfigReferenceGit(BaseModel):
+ repository: str
+
+ branch: Optional[str] = None
+
+ description: Optional[str] = None
+
+ hidden: Optional[bool] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_reference_local.py b/src/opencode/types/shared/config_reference_local.py
new file mode 100644
index 0000000..bee10ce
--- /dev/null
+++ b/src/opencode/types/shared/config_reference_local.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ConfigReferenceLocal"]
+
+
+class ConfigReferenceLocal(BaseModel):
+ path: str
+
+ description: Optional[str] = None
+
+ hidden: Optional[bool] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_shell_option.py b/src/opencode/types/shared/config_shell_option.py
new file mode 100644
index 0000000..4700760
--- /dev/null
+++ b/src/opencode/types/shared/config_shell_option.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ConfigShellOption"]
+
+
+class ConfigShellOption(BaseModel):
+ acceptable: bool
+
+ name: str
+
+ path: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_updated_event.py b/src/opencode/types/shared/config_updated_event.py
new file mode 100644
index 0000000..e1a7f7d
--- /dev/null
+++ b/src/opencode/types/shared/config_updated_event.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["ConfigUpdatedEvent"]
+
+
+class ConfigUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: object
+
+ type: Literal["config.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_warming.py b/src/opencode/types/shared/config_warming.py
new file mode 100644
index 0000000..3e058e3
--- /dev/null
+++ b/src/opencode/types/shared/config_warming.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ConfigWarming"]
+
+
+class ConfigWarming(BaseModel):
+ duration: Optional[str] = None
+
+ interval: Optional[str] = None
+
+ prompt: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_web_search_info.py b/src/opencode/types/shared/config_web_search_info.py
new file mode 100644
index 0000000..9dd9e92
--- /dev/null
+++ b/src/opencode/types/shared/config_web_search_info.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .web_search_id import WebSearchID
+
+__all__ = ["ConfigWebSearchInfo", "Provider"]
+
+Provider: TypeAlias = Union[Literal["random"], WebSearchID]
+
+
+class ConfigWebSearchInfo(BaseModel):
+ provider: Provider
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/config_worktree.py b/src/opencode/types/shared/config_worktree.py
new file mode 100644
index 0000000..d5b203e
--- /dev/null
+++ b/src/opencode/types/shared/config_worktree.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ConfigWorktree"]
+
+
+class ConfigWorktree(BaseModel):
+ directory: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/connection_credential_info.py b/src/opencode/types/shared/connection_credential_info.py
new file mode 100644
index 0000000..40f27f6
--- /dev/null
+++ b/src/opencode/types/shared/connection_credential_info.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .credential_id import CredentialID
+from .connection_status import ConnectionStatus
+
+__all__ = ["ConnectionCredentialInfo"]
+
+
+class ConnectionCredentialInfo(BaseModel):
+ id: CredentialID
+
+ label: str
+
+ method: Literal["key", "oauth", "external"]
+
+ type: Literal["credential"]
+
+ status: Optional[ConnectionStatus] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/connection_env_info.py b/src/opencode/types/shared/connection_env_info.py
new file mode 100644
index 0000000..b99aa8f
--- /dev/null
+++ b/src/opencode/types/shared/connection_env_info.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .connection_status import ConnectionStatus
+
+__all__ = ["ConnectionEnvInfo"]
+
+
+class ConnectionEnvInfo(BaseModel):
+ name: str
+
+ type: Literal["env"]
+
+ status: Optional[ConnectionStatus] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/connection_info.py b/src/opencode/types/shared/connection_info.py
new file mode 100644
index 0000000..69e21fe
--- /dev/null
+++ b/src/opencode/types/shared/connection_info.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import TypeAlias
+
+from .connection_env_info import ConnectionEnvInfo
+from .connection_credential_info import ConnectionCredentialInfo
+
+__all__ = ["ConnectionInfo"]
+
+ConnectionInfo: TypeAlias = Union[ConnectionCredentialInfo, ConnectionEnvInfo]
diff --git a/src/opencode/types/shared/connection_status.py b/src/opencode/types/shared/connection_status.py
new file mode 100644
index 0000000..11a8a87
--- /dev/null
+++ b/src/opencode/types/shared/connection_status.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ConnectionStatus"]
+
+
+class ConnectionStatus(BaseModel):
+ message: str
+
+ status: Literal["needs_auth"]
+
+ url: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/credential_entry.py b/src/opencode/types/shared/credential_entry.py
new file mode 100644
index 0000000..a99c95f
--- /dev/null
+++ b/src/opencode/types/shared/credential_entry.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .credential_id import CredentialID
+from .integration_id import IntegrationID
+from .credential_value import CredentialValue
+
+__all__ = ["CredentialEntry"]
+
+
+class CredentialEntry(BaseModel):
+ id: CredentialID
+
+ active: bool
+
+ integration_id: IntegrationID = FieldInfo(alias="integrationID")
+
+ label: str
+
+ value: CredentialValue
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/credential_external.py b/src/opencode/types/shared/credential_external.py
new file mode 100644
index 0000000..0f520a9
--- /dev/null
+++ b/src/opencode/types/shared/credential_external.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .integration_method_id import IntegrationMethodID
+
+__all__ = ["CredentialExternal"]
+
+
+class CredentialExternal(BaseModel):
+ method_id: IntegrationMethodID = FieldInfo(alias="methodID")
+
+ type: Literal["external"]
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/credential_id.py b/src/opencode/types/shared/credential_id.py
new file mode 100644
index 0000000..4d119e7
--- /dev/null
+++ b/src/opencode/types/shared/credential_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["CredentialID"]
+
+CredentialID: TypeAlias = str
diff --git a/src/opencode/types/shared/credential_key.py b/src/opencode/types/shared/credential_key.py
new file mode 100644
index 0000000..5b2699e
--- /dev/null
+++ b/src/opencode/types/shared/credential_key.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["CredentialKey"]
+
+
+class CredentialKey(BaseModel):
+ key: str
+
+ type: Literal["key"]
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/credential_o_auth.py b/src/opencode/types/shared/credential_o_auth.py
new file mode 100644
index 0000000..4730d18
--- /dev/null
+++ b/src/opencode/types/shared/credential_o_auth.py
@@ -0,0 +1,37 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .integration_method_id import IntegrationMethodID
+
+__all__ = ["CredentialOAuth"]
+
+
+class CredentialOAuth(BaseModel):
+ access: str
+
+ expires: int
+
+ method_id: IntegrationMethodID = FieldInfo(alias="methodID")
+
+ refresh: str
+
+ type: Literal["oauth"]
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/credential_switched_event.py b/src/opencode/types/shared/credential_switched_event.py
new file mode 100644
index 0000000..0efc903
--- /dev/null
+++ b/src/opencode/types/shared/credential_switched_event.py
@@ -0,0 +1,58 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+from .credential_id import CredentialID
+from .integration_id import IntegrationID
+
+__all__ = ["CredentialSwitchedEvent", "Data"]
+
+
+class Data(BaseModel):
+ credential_id: Optional[CredentialID] = FieldInfo(alias="credentialID", default=None)
+
+ integration_id: IntegrationID = FieldInfo(alias="integrationID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class CredentialSwitchedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["credential.switched"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/credential_updated_event.py b/src/opencode/types/shared/credential_updated_event.py
new file mode 100644
index 0000000..c8d1da9
--- /dev/null
+++ b/src/opencode/types/shared/credential_updated_event.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["CredentialUpdatedEvent"]
+
+
+class CredentialUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: object
+
+ type: Literal["credential.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/credential_value.py b/src/opencode/types/shared/credential_value.py
new file mode 100644
index 0000000..8abc833
--- /dev/null
+++ b/src/opencode/types/shared/credential_value.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import TypeAlias
+
+from .credential_key import CredentialKey
+from .credential_o_auth import CredentialOAuth
+from .credential_external import CredentialExternal
+
+__all__ = ["CredentialValue"]
+
+CredentialValue: TypeAlias = Union[CredentialOAuth, CredentialKey, CredentialExternal]
diff --git a/src/opencode/types/shared/event.py b/src/opencode/types/shared/event.py
new file mode 100644
index 0000000..30080f6
--- /dev/null
+++ b/src/opencode/types/shared/event.py
@@ -0,0 +1,202 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import TypeAlias
+
+from .rpc_event import RpcEvent
+from .pty_exited_event import PtyExitedEvent
+from .pty_created_event import PtyCreatedEvent
+from .pty_deleted_event import PtyDeletedEvent
+from .pty_updated_event import PtyUpdatedEvent
+from .form_created_event import FormCreatedEvent
+from .form_replied_event import FormRepliedEvent
+from .session_idle_event import SessionIdleEvent
+from .shell_exited_event import ShellExitedEvent
+from .agent_updated_event import AgentUpdatedEvent
+from .model_updated_event import ModelUpdatedEvent
+from .session_moved_event import SessionMovedEvent
+from .shell_created_event import ShellCreatedEvent
+from .shell_deleted_event import ShellDeletedEvent
+from .skill_updated_event import SkillUpdatedEvent
+from .config_updated_event import ConfigUpdatedEvent
+from .form_cancelled_event import FormCancelledEvent
+from .plugin_updated_event import PluginUpdatedEvent
+from .session_forked_event import SessionForkedEvent
+from .session_viewed_event import SessionViewedEvent
+from .tui_toast_show_event import TuiToastShowEvent
+from .command_updated_event import CommandUpdatedEvent
+from .project_updated_event import ProjectUpdatedEvent
+from .session_created_event import SessionCreatedEvent
+from .session_deleted_event import SessionDeletedEvent
+from .session_renamed_event import SessionRenamedEvent
+from .permission_asked_event import PermissionAskedEvent
+from .provider_updated_event import ProviderUpdatedEvent
+from .server_connected_event import ServerConnectedEvent
+from .session_archived_event import SessionArchivedEvent
+from .session_status_updated import SessionStatusUpdated
+from .worktree_updated_event import WorktreeUpdatedEvent
+from .location_shutdown_event import LocationShutdownEvent
+from .reference_updated_event import ReferenceUpdatedEvent
+from .session_synthetic_event import SessionSyntheticEvent
+from .tui_prompt_append_event import TuiPromptAppendEvent
+from .websearch_updated_event import WebsearchUpdatedEvent
+from .worktree_resolved_event import WorktreeResolvedEvent
+from .credential_updated_event import CredentialUpdatedEvent
+from .filesystem_changed_event import FilesystemChangedEvent
+from .mcp_status_changed_event import McpStatusChangedEvent
+from .permission_replied_event import PermissionRepliedEvent
+from .session_step_ended_event import SessionStepEndedEvent
+from .session_text_delta_event import SessionTextDeltaEvent
+from .session_text_ended_event import SessionTextEndedEvent
+from .session_unarchived_event import SessionUnarchivedEvent
+from .tui_session_select_event import TuiSessionSelectEvent
+from .vcs_branch_updated_event import VcsBranchUpdatedEvent
+from .credential_switched_event import CredentialSwitchedEvent
+from .integration_updated_event import IntegrationUpdatedEvent
+from .session_permissions_event import SessionPermissionsEvent
+from .session_shell_ended_event import SessionShellEndedEvent
+from .session_step_failed_event import SessionStepFailedEvent
+from .session_tool_called_event import SessionToolCalledEvent
+from .session_tool_failed_event import SessionToolFailedEvent
+from .tui_command_execute_event import TuiCommandExecuteEvent
+from .installation_updated_event import InstallationUpdatedEvent
+from .models_dev_refreshed_event import ModelsDevRefreshedEvent
+from .persistent_pty_added_event import PersistentPtyAddedEvent
+from .session_step_started_event import SessionStepStartedEvent
+from .session_text_started_event import SessionTextStartedEvent
+from .session_tool_success_event import SessionToolSuccessEvent
+from .mcp_resources_changed_event import McpResourcesChangedEvent
+from .session_revert_staged_event import SessionRevertStagedEvent
+from .session_shell_started_event import SessionShellStartedEvent
+from .session_step_streamed_event import SessionStepStreamedEvent
+from .session_tool_progress_event import SessionToolProgressEvent
+from .session_usage_updated_event import SessionUsageUpdatedEvent
+from .persistent_pty_removed_event import PersistentPtyRemovedEvent
+from .session_agent_selected_event import SessionAgentSelectedEvent
+from .session_inbox_enqueued_event import SessionInboxEnqueuedEvent
+from .session_model_selected_event import SessionModelSelectedEvent
+from .session_revert_cleared_event import SessionRevertClearedEvent
+from .session_inbox_cancelled_event import SessionInboxCancelledEvent
+from .session_inbox_delivered_event import SessionInboxDeliveredEvent
+from .session_reasoning_delta_event import SessionReasoningDeltaEvent
+from .session_reasoning_ended_event import SessionReasoningEndedEvent
+from .session_retry_scheduled_event import SessionRetryScheduledEvent
+from .session_skill_activated_event import SessionSkillActivatedEvent
+from .session_compaction_delta_event import SessionCompactionDeltaEvent
+from .session_compaction_ended_event import SessionCompactionEndedEvent
+from .session_execution_failed_event import SessionExecutionFailedEvent
+from .session_metadata_updated_event import SessionMetadataUpdatedEvent
+from .session_revert_committed_event import SessionRevertCommittedEvent
+from .session_tool_input_delta_event import SessionToolInputDeltaEvent
+from .session_tool_input_ended_event import SessionToolInputEndedEvent
+from .session_compaction_failed_event import SessionCompactionFailedEvent
+from .session_execution_started_event import SessionExecutionStartedEvent
+from .session_reasoning_started_event import SessionReasoningStartedEvent
+from .session_compaction_started_event import SessionCompactionStartedEvent
+from .session_tool_input_started_event import SessionToolInputStartedEvent
+from .session_execution_succeeded_event import SessionExecutionSucceededEvent
+from .session_instructions_updated_event import SessionInstructionsUpdatedEvent
+from .installation_update_available_event import InstallationUpdateAvailableEvent
+from .session_execution_interrupted_event import SessionExecutionInterruptedEvent
+from .session_inbox_delivery_changed_event import SessionInboxDeliveryChangedEvent
+
+__all__ = ["Event"]
+
+Event: TypeAlias = Union[
+ LocationShutdownEvent,
+ ModelsDevRefreshedEvent,
+ CredentialUpdatedEvent,
+ CredentialSwitchedEvent,
+ IntegrationUpdatedEvent,
+ ProviderUpdatedEvent,
+ ModelUpdatedEvent,
+ AgentUpdatedEvent,
+ SessionCreatedEvent,
+ SessionAgentSelectedEvent,
+ SessionModelSelectedEvent,
+ SessionMovedEvent,
+ SessionRenamedEvent,
+ SessionMetadataUpdatedEvent,
+ SessionPermissionsEvent,
+ SessionViewedEvent,
+ SessionArchivedEvent,
+ SessionUnarchivedEvent,
+ SessionUsageUpdatedEvent,
+ SessionDeletedEvent,
+ SessionForkedEvent,
+ SessionInboxDeliveredEvent,
+ SessionInboxEnqueuedEvent,
+ SessionInboxCancelledEvent,
+ SessionInboxDeliveryChangedEvent,
+ SessionExecutionStartedEvent,
+ SessionExecutionSucceededEvent,
+ SessionExecutionFailedEvent,
+ SessionExecutionInterruptedEvent,
+ SessionInstructionsUpdatedEvent,
+ SessionSyntheticEvent,
+ SessionSkillActivatedEvent,
+ SessionShellStartedEvent,
+ SessionShellEndedEvent,
+ SessionStepStartedEvent,
+ SessionStepStreamedEvent,
+ SessionStepEndedEvent,
+ SessionStepFailedEvent,
+ SessionTextStartedEvent,
+ SessionTextDeltaEvent,
+ SessionTextEndedEvent,
+ SessionReasoningStartedEvent,
+ SessionReasoningDeltaEvent,
+ SessionReasoningEndedEvent,
+ SessionToolInputStartedEvent,
+ SessionToolInputDeltaEvent,
+ SessionToolInputEndedEvent,
+ SessionToolCalledEvent,
+ SessionToolProgressEvent,
+ SessionToolSuccessEvent,
+ SessionToolFailedEvent,
+ SessionRetryScheduledEvent,
+ SessionCompactionStartedEvent,
+ SessionCompactionDeltaEvent,
+ SessionCompactionEndedEvent,
+ SessionCompactionFailedEvent,
+ SessionRevertStagedEvent,
+ SessionRevertClearedEvent,
+ SessionRevertCommittedEvent,
+ FilesystemChangedEvent,
+ ReferenceUpdatedEvent,
+ PermissionAskedEvent,
+ PermissionRepliedEvent,
+ PluginUpdatedEvent,
+ ProjectUpdatedEvent,
+ WorktreeUpdatedEvent,
+ WorktreeResolvedEvent,
+ CommandUpdatedEvent,
+ ConfigUpdatedEvent,
+ SkillUpdatedEvent,
+ PtyCreatedEvent,
+ PtyUpdatedEvent,
+ PtyExitedEvent,
+ PtyDeletedEvent,
+ PersistentPtyAddedEvent,
+ PersistentPtyRemovedEvent,
+ ShellCreatedEvent,
+ ShellExitedEvent,
+ ShellDeletedEvent,
+ FormCreatedEvent,
+ FormRepliedEvent,
+ FormCancelledEvent,
+ WebsearchUpdatedEvent,
+ SessionStatusUpdated,
+ SessionIdleEvent,
+ TuiPromptAppendEvent,
+ TuiCommandExecuteEvent,
+ TuiToastShowEvent,
+ TuiSessionSelectEvent,
+ InstallationUpdatedEvent,
+ InstallationUpdateAvailableEvent,
+ VcsBranchUpdatedEvent,
+ McpStatusChangedEvent,
+ McpResourcesChangedEvent,
+ RpcEvent,
+ ServerConnectedEvent,
+]
diff --git a/src/opencode/types/shared/event_id.py b/src/opencode/types/shared/event_id.py
new file mode 100644
index 0000000..8c5b3d8
--- /dev/null
+++ b/src/opencode/types/shared/event_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["EventID"]
+
+EventID: TypeAlias = str
diff --git a/src/opencode/types/shared/event_seq.py b/src/opencode/types/shared/event_seq.py
new file mode 100644
index 0000000..8a30363
--- /dev/null
+++ b/src/opencode/types/shared/event_seq.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["EventSeq"]
+
+EventSeq: TypeAlias = int
diff --git a/src/opencode/types/shared/event_version.py b/src/opencode/types/shared/event_version.py
new file mode 100644
index 0000000..632c3a8
--- /dev/null
+++ b/src/opencode/types/shared/event_version.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["EventVersion"]
+
+EventVersion: TypeAlias = Literal[1]
diff --git a/src/opencode/types/shared/event_version10.py b/src/opencode/types/shared/event_version10.py
new file mode 100644
index 0000000..f72a77e
--- /dev/null
+++ b/src/opencode/types/shared/event_version10.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["EventVersion10"]
+
+EventVersion10: TypeAlias = Literal[2]
diff --git a/src/opencode/types/shared/file_diff_info.py b/src/opencode/types/shared/file_diff_info.py
new file mode 100644
index 0000000..179b8bd
--- /dev/null
+++ b/src/opencode/types/shared/file_diff_info.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["FileDiffInfo"]
+
+
+class FileDiffInfo(BaseModel):
+ additions: int
+
+ deletions: int
+
+ file: str
+
+ patch: str
+
+ status: Literal["added", "deleted", "modified"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/file_system_entry.py b/src/opencode/types/shared/file_system_entry.py
new file mode 100644
index 0000000..debbaba
--- /dev/null
+++ b/src/opencode/types/shared/file_system_entry.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .relative_path import RelativePath
+
+__all__ = ["FileSystemEntry"]
+
+
+class FileSystemEntry(BaseModel):
+ path: RelativePath
+
+ type: Literal["file", "directory"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/file_system_write.py b/src/opencode/types/shared/file_system_write.py
new file mode 100644
index 0000000..e5fad4e
--- /dev/null
+++ b/src/opencode/types/shared/file_system_write.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .absolute_path import AbsolutePath
+
+__all__ = ["FileSystemWrite"]
+
+
+class FileSystemWrite(BaseModel):
+ path: AbsolutePath
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/filesystem_changed_event.py b/src/opencode/types/shared/filesystem_changed_event.py
new file mode 100644
index 0000000..6d6195d
--- /dev/null
+++ b/src/opencode/types/shared/filesystem_changed_event.py
@@ -0,0 +1,56 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["FilesystemChangedEvent", "Data"]
+
+
+class Data(BaseModel):
+ event: Literal["add", "change", "unlink"]
+
+ file: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class FilesystemChangedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["filesystem.changed"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/form_answer.py b/src/opencode/types/shared/form_answer.py
new file mode 100644
index 0000000..838ec1f
--- /dev/null
+++ b/src/opencode/types/shared/form_answer.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+from .form_value import FormValue
+
+__all__ = ["FormAnswer"]
+
+FormAnswer: TypeAlias = Dict[str, FormValue]
diff --git a/src/opencode/types/shared/form_boolean_field.py b/src/opencode/types/shared/form_boolean_field.py
new file mode 100644
index 0000000..0f85a3a
--- /dev/null
+++ b/src/opencode/types/shared/form_boolean_field.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .form_when import FormWhen
+
+__all__ = ["FormBooleanField"]
+
+
+class FormBooleanField(BaseModel):
+ key: str
+
+ type: Literal["boolean"]
+
+ default: Optional[bool] = None
+
+ description: Optional[str] = None
+
+ hidden: Optional[bool] = None
+
+ required: Optional[bool] = None
+
+ title: Optional[str] = None
+
+ when: Optional[List[FormWhen]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/form_cancelled_event.py b/src/opencode/types/shared/form_cancelled_event.py
new file mode 100644
index 0000000..49400ab
--- /dev/null
+++ b/src/opencode/types/shared/form_cancelled_event.py
@@ -0,0 +1,57 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .form_id import FormID
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["FormCancelledEvent", "Data"]
+
+
+class Data(BaseModel):
+ id: FormID
+
+ session_id: str = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class FormCancelledEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["form.cancelled"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/form_created_event.py b/src/opencode/types/shared/form_created_event.py
new file mode 100644
index 0000000..d8ae7eb
--- /dev/null
+++ b/src/opencode/types/shared/form_created_event.py
@@ -0,0 +1,55 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .form_info import FormInfo
+from .location_ref import LocationRef
+
+__all__ = ["FormCreatedEvent", "Data"]
+
+
+class Data(BaseModel):
+ form: FormInfo
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class FormCreatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["form.created"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/form_detail.py b/src/opencode/types/shared/form_detail.py
new file mode 100644
index 0000000..3fc2ad2
--- /dev/null
+++ b/src/opencode/types/shared/form_detail.py
@@ -0,0 +1,39 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from .form_id import FormID
+from ..._models import BaseModel
+from .form_state import FormState
+from .form_fields import FormFields
+from .form_metadata import FormMetadata
+
+__all__ = ["FormDetail"]
+
+
+class FormDetail(BaseModel):
+ id: FormID
+
+ fields: FormFields
+
+ session_id: str = FieldInfo(alias="sessionID")
+
+ state: FormState
+
+ title: str
+
+ metadata: Optional[FormMetadata] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/form_external_field.py b/src/opencode/types/shared/form_external_field.py
new file mode 100644
index 0000000..2e17122
--- /dev/null
+++ b/src/opencode/types/shared/form_external_field.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["FormExternalField"]
+
+
+class FormExternalField(BaseModel):
+ key: str
+
+ type: Literal["external"]
+
+ url: str
+
+ description: Optional[str] = None
+
+ title: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/form_field.py b/src/opencode/types/shared/form_field.py
new file mode 100644
index 0000000..31cbf76
--- /dev/null
+++ b/src/opencode/types/shared/form_field.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import TypeAlias
+
+from .form_number_field import FormNumberField
+from .form_string_field import FormStringField
+from .form_boolean_field import FormBooleanField
+from .form_integer_field import FormIntegerField
+from .form_external_field import FormExternalField
+from .form_multiselect_field import FormMultiselectField
+
+__all__ = ["FormField"]
+
+FormField: TypeAlias = Union[
+ FormStringField, FormNumberField, FormIntegerField, FormBooleanField, FormMultiselectField, FormExternalField
+]
diff --git a/src/opencode/types/shared/form_fields.py b/src/opencode/types/shared/form_fields.py
new file mode 100644
index 0000000..f82cea8
--- /dev/null
+++ b/src/opencode/types/shared/form_fields.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+from .form_field import FormField
+
+__all__ = ["FormFields"]
+
+FormFields: TypeAlias = List[FormField]
diff --git a/src/opencode/types/shared/form_fields1.py b/src/opencode/types/shared/form_fields1.py
new file mode 100644
index 0000000..8504a1a
--- /dev/null
+++ b/src/opencode/types/shared/form_fields1.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+from .form_field import FormField
+
+__all__ = ["FormFields1"]
+
+FormFields1: TypeAlias = List[FormField]
diff --git a/src/opencode/types/shared/form_fields2.py b/src/opencode/types/shared/form_fields2.py
new file mode 100644
index 0000000..6d08a22
--- /dev/null
+++ b/src/opencode/types/shared/form_fields2.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+from .form_field import FormField
+
+__all__ = ["FormFields2"]
+
+FormFields2: TypeAlias = List[FormField]
diff --git a/src/opencode/types/shared/form_fields3.py b/src/opencode/types/shared/form_fields3.py
new file mode 100644
index 0000000..0210ad5
--- /dev/null
+++ b/src/opencode/types/shared/form_fields3.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+from .form_field import FormField
+
+__all__ = ["FormFields3"]
+
+FormFields3: TypeAlias = List[FormField]
diff --git a/src/opencode/types/shared/form_id.py b/src/opencode/types/shared/form_id.py
new file mode 100644
index 0000000..0b6201f
--- /dev/null
+++ b/src/opencode/types/shared/form_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["FormID"]
+
+FormID: TypeAlias = str
diff --git a/src/opencode/types/shared/form_info.py b/src/opencode/types/shared/form_info.py
new file mode 100644
index 0000000..01eb8e7
--- /dev/null
+++ b/src/opencode/types/shared/form_info.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from .form_id import FormID
+from ..._models import BaseModel
+from .form_fields import FormFields
+from .form_metadata import FormMetadata
+
+__all__ = ["FormInfo"]
+
+
+class FormInfo(BaseModel):
+ id: FormID
+
+ fields: FormFields
+
+ session_id: str = FieldInfo(alias="sessionID")
+
+ title: str
+
+ metadata: Optional[FormMetadata] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/form_integer_field.py b/src/opencode/types/shared/form_integer_field.py
new file mode 100644
index 0000000..79b6e45
--- /dev/null
+++ b/src/opencode/types/shared/form_integer_field.py
@@ -0,0 +1,45 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Union, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .form_when import FormWhen
+
+__all__ = ["FormIntegerField"]
+
+
+class FormIntegerField(BaseModel):
+ key: str
+
+ type: Literal["integer"]
+
+ default: Union[Literal["Infinity", "-Infinity", "NaN"], float, None] = None
+
+ description: Optional[str] = None
+
+ hidden: Optional[bool] = None
+
+ maximum: Union[Literal["Infinity", "-Infinity", "NaN"], float, None] = None
+
+ minimum: Union[Literal["Infinity", "-Infinity", "NaN"], float, None] = None
+
+ required: Optional[bool] = None
+
+ title: Optional[str] = None
+
+ when: Optional[List[FormWhen]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/form_metadata.py b/src/opencode/types/shared/form_metadata.py
new file mode 100644
index 0000000..840b941
--- /dev/null
+++ b/src/opencode/types/shared/form_metadata.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["FormMetadata"]
+
+FormMetadata: TypeAlias = Dict[str, object]
diff --git a/src/opencode/types/shared/form_multiselect_field.py b/src/opencode/types/shared/form_multiselect_field.py
new file mode 100644
index 0000000..f14e9a4
--- /dev/null
+++ b/src/opencode/types/shared/form_multiselect_field.py
@@ -0,0 +1,50 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .form_when import FormWhen
+from .form_option import FormOption
+
+__all__ = ["FormMultiselectField"]
+
+
+class FormMultiselectField(BaseModel):
+ key: str
+
+ options: List[FormOption]
+
+ type: Literal["multiselect"]
+
+ custom: Optional[bool] = None
+
+ default: Optional[List[str]] = None
+
+ description: Optional[str] = None
+
+ hidden: Optional[bool] = None
+
+ max_items: Optional[int] = FieldInfo(alias="maxItems", default=None)
+
+ min_items: Optional[int] = FieldInfo(alias="minItems", default=None)
+
+ required: Optional[bool] = None
+
+ title: Optional[str] = None
+
+ when: Optional[List[FormWhen]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/form_number_field.py b/src/opencode/types/shared/form_number_field.py
new file mode 100644
index 0000000..a343144
--- /dev/null
+++ b/src/opencode/types/shared/form_number_field.py
@@ -0,0 +1,45 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Union, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .form_when import FormWhen
+
+__all__ = ["FormNumberField"]
+
+
+class FormNumberField(BaseModel):
+ key: str
+
+ type: Literal["number"]
+
+ default: Union[Literal["Infinity", "-Infinity", "NaN"], float, None] = None
+
+ description: Optional[str] = None
+
+ hidden: Optional[bool] = None
+
+ maximum: Union[Literal["Infinity", "-Infinity", "NaN"], float, None] = None
+
+ minimum: Union[Literal["Infinity", "-Infinity", "NaN"], float, None] = None
+
+ required: Optional[bool] = None
+
+ title: Optional[str] = None
+
+ when: Optional[List[FormWhen]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/form_option.py b/src/opencode/types/shared/form_option.py
new file mode 100644
index 0000000..95a114a
--- /dev/null
+++ b/src/opencode/types/shared/form_option.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["FormOption"]
+
+
+class FormOption(BaseModel):
+ label: str
+
+ value: str
+
+ description: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/form_replied_event.py b/src/opencode/types/shared/form_replied_event.py
new file mode 100644
index 0000000..e90525b
--- /dev/null
+++ b/src/opencode/types/shared/form_replied_event.py
@@ -0,0 +1,60 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .form_id import FormID
+from .event_id import EventID
+from ..._models import BaseModel
+from .form_answer import FormAnswer
+from .location_ref import LocationRef
+
+__all__ = ["FormRepliedEvent", "Data"]
+
+
+class Data(BaseModel):
+ id: FormID
+
+ answer: FormAnswer
+
+ session_id: str = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class FormRepliedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["form.replied"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/form_state.py b/src/opencode/types/shared/form_state.py
new file mode 100644
index 0000000..36fe945
--- /dev/null
+++ b/src/opencode/types/shared/form_state.py
@@ -0,0 +1,66 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union, Optional
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .form_answer import FormAnswer
+
+__all__ = ["FormState", "FormStateVariant0", "FormStateVariant1", "FormStateVariant2"]
+
+
+class FormStateVariant0(BaseModel):
+ status: Literal["pending"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class FormStateVariant1(BaseModel):
+ answer: FormAnswer
+
+ status: Literal["answered"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class FormStateVariant2(BaseModel):
+ status: Literal["cancelled"]
+
+ message: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+FormState: TypeAlias = Union[FormStateVariant0, FormStateVariant1, FormStateVariant2]
diff --git a/src/opencode/types/shared/form_string_field.py b/src/opencode/types/shared/form_string_field.py
new file mode 100644
index 0000000..8e1a165
--- /dev/null
+++ b/src/opencode/types/shared/form_string_field.py
@@ -0,0 +1,56 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .form_when import FormWhen
+from .form_option import FormOption
+
+__all__ = ["FormStringField"]
+
+
+class FormStringField(BaseModel):
+ key: str
+
+ type: Literal["string"]
+
+ custom: Optional[bool] = None
+
+ default: Optional[str] = None
+
+ description: Optional[str] = None
+
+ format: Optional[Literal["email", "uri", "date", "date-time"]] = None
+
+ hidden: Optional[bool] = None
+
+ max_length: Optional[int] = FieldInfo(alias="maxLength", default=None)
+
+ min_length: Optional[int] = FieldInfo(alias="minLength", default=None)
+
+ options: Optional[List[FormOption]] = None
+
+ pattern: Optional[str] = None
+
+ placeholder: Optional[str] = None
+
+ required: Optional[bool] = None
+
+ title: Optional[str] = None
+
+ when: Optional[List[FormWhen]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/form_value.py b/src/opencode/types/shared/form_value.py
new file mode 100644
index 0000000..96a2d99
--- /dev/null
+++ b/src/opencode/types/shared/form_value.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import List, Union
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["FormValue"]
+
+FormValue: TypeAlias = Union[Literal["Infinity", "-Infinity", "NaN"], str, float, bool, List[str]]
diff --git a/src/opencode/types/shared/form_when.py b/src/opencode/types/shared/form_when.py
new file mode 100644
index 0000000..c04bffa
--- /dev/null
+++ b/src/opencode/types/shared/form_when.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["FormWhen"]
+
+
+class FormWhen(BaseModel):
+ key: str
+
+ op: Literal["eq", "neq"]
+
+ value: Union[Literal["Infinity", "-Infinity", "NaN"], str, float, bool]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/generate_text_response.py b/src/opencode/types/shared/generate_text_response.py
new file mode 100644
index 0000000..913cc75
--- /dev/null
+++ b/src/opencode/types/shared/generate_text_response.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["GenerateTextResponse", "Data"]
+
+
+class Data(BaseModel):
+ text: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class GenerateTextResponse(BaseModel):
+ data: Data
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/installation_update_available_event.py b/src/opencode/types/shared/installation_update_available_event.py
new file mode 100644
index 0000000..78889d4
--- /dev/null
+++ b/src/opencode/types/shared/installation_update_available_event.py
@@ -0,0 +1,54 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["InstallationUpdateAvailableEvent", "Data"]
+
+
+class Data(BaseModel):
+ version: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class InstallationUpdateAvailableEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["installation.update-available"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/installation_updated_event.py b/src/opencode/types/shared/installation_updated_event.py
new file mode 100644
index 0000000..dab6f07
--- /dev/null
+++ b/src/opencode/types/shared/installation_updated_event.py
@@ -0,0 +1,54 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["InstallationUpdatedEvent", "Data"]
+
+
+class Data(BaseModel):
+ version: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class InstallationUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["installation.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/instruction_entry_info.py b/src/opencode/types/shared/instruction_entry_info.py
new file mode 100644
index 0000000..7dd0a9c
--- /dev/null
+++ b/src/opencode/types/shared/instruction_entry_info.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .instruction_entry_key import InstructionEntryKey
+
+__all__ = ["InstructionEntryInfo"]
+
+
+class InstructionEntryInfo(BaseModel):
+ key: InstructionEntryKey
+ """Instruction entry key (lowercase alphanumerics plus . _ -)"""
+
+ value: object
+ """JSON value attached to the session's instructions"""
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/instruction_entry_key.py b/src/opencode/types/shared/instruction_entry_key.py
new file mode 100644
index 0000000..0580847
--- /dev/null
+++ b/src/opencode/types/shared/instruction_entry_key.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["InstructionEntryKey"]
+
+InstructionEntryKey: TypeAlias = str
diff --git a/src/opencode/types/shared/instruction_entry_snapshot.py b/src/opencode/types/shared/instruction_entry_snapshot.py
new file mode 100644
index 0000000..8a39263
--- /dev/null
+++ b/src/opencode/types/shared/instruction_entry_snapshot.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+from typing_extensions import TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .instruction_entry_key import InstructionEntryKey
+
+__all__ = ["InstructionEntrySnapshot", "InstructionEntrySnapshotItem"]
+
+
+class InstructionEntrySnapshotItem(BaseModel):
+ key: InstructionEntryKey
+ """Instruction entry key (lowercase alphanumerics plus . _ -)"""
+
+ removed: bool
+
+ value: object
+ """JSON value attached to the session's instructions"""
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+InstructionEntrySnapshot: TypeAlias = List[InstructionEntrySnapshotItem]
diff --git a/src/opencode/types/shared/instruction_hash.py b/src/opencode/types/shared/instruction_hash.py
new file mode 100644
index 0000000..256fd11
--- /dev/null
+++ b/src/opencode/types/shared/instruction_hash.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["InstructionHash"]
+
+InstructionHash: TypeAlias = str
diff --git a/src/opencode/types/shared/integration_attempt.py b/src/opencode/types/shared/integration_attempt.py
new file mode 100644
index 0000000..dfdf394
--- /dev/null
+++ b/src/opencode/types/shared/integration_attempt.py
@@ -0,0 +1,53 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .integration_attempt_id import IntegrationAttemptID
+
+__all__ = ["IntegrationAttempt", "Time"]
+
+
+class Time(BaseModel):
+ created: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ expires: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class IntegrationAttempt(BaseModel):
+ attempt_id: IntegrationAttemptID = FieldInfo(alias="attemptID")
+
+ instructions: str
+
+ mode: Literal["auto", "code"]
+
+ time: Time
+
+ url: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/integration_attempt_id.py b/src/opencode/types/shared/integration_attempt_id.py
new file mode 100644
index 0000000..347df95
--- /dev/null
+++ b/src/opencode/types/shared/integration_attempt_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["IntegrationAttemptID"]
+
+IntegrationAttemptID: TypeAlias = str
diff --git a/src/opencode/types/shared/integration_attempt_status.py b/src/opencode/types/shared/integration_attempt_status.py
new file mode 100644
index 0000000..6c992ee
--- /dev/null
+++ b/src/opencode/types/shared/integration_attempt_status.py
@@ -0,0 +1,174 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = [
+ "IntegrationAttemptStatus",
+ "IntegrationAttemptStatusVariant0",
+ "IntegrationAttemptStatusVariant0Time",
+ "IntegrationAttemptStatusVariant1",
+ "IntegrationAttemptStatusVariant1Time",
+ "IntegrationAttemptStatusVariant2",
+ "IntegrationAttemptStatusVariant2Time",
+ "IntegrationAttemptStatusVariant3",
+ "IntegrationAttemptStatusVariant3Time",
+]
+
+
+class IntegrationAttemptStatusVariant0Time(BaseModel):
+ created: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ expires: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class IntegrationAttemptStatusVariant0(BaseModel):
+ status: Literal["pending"]
+
+ time: IntegrationAttemptStatusVariant0Time
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class IntegrationAttemptStatusVariant1Time(BaseModel):
+ created: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ expires: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class IntegrationAttemptStatusVariant1(BaseModel):
+ status: Literal["complete"]
+
+ time: IntegrationAttemptStatusVariant1Time
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class IntegrationAttemptStatusVariant2Time(BaseModel):
+ created: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ expires: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class IntegrationAttemptStatusVariant2(BaseModel):
+ message: str
+
+ status: Literal["failed"]
+
+ time: IntegrationAttemptStatusVariant2Time
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class IntegrationAttemptStatusVariant3Time(BaseModel):
+ created: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ expires: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class IntegrationAttemptStatusVariant3(BaseModel):
+ status: Literal["expired"]
+
+ time: IntegrationAttemptStatusVariant3Time
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+IntegrationAttemptStatus: TypeAlias = Union[
+ IntegrationAttemptStatusVariant0,
+ IntegrationAttemptStatusVariant1,
+ IntegrationAttemptStatusVariant2,
+ IntegrationAttemptStatusVariant3,
+]
diff --git a/src/opencode/types/shared/integration_command_attempt.py b/src/opencode/types/shared/integration_command_attempt.py
new file mode 100644
index 0000000..0be6bab
--- /dev/null
+++ b/src/opencode/types/shared/integration_command_attempt.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .integration_attempt_id import IntegrationAttemptID
+
+__all__ = ["IntegrationCommandAttempt", "Time"]
+
+
+class Time(BaseModel):
+ created: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ expires: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class IntegrationCommandAttempt(BaseModel):
+ attempt_id: IntegrationAttemptID = FieldInfo(alias="attemptID")
+
+ time: Time
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/integration_command_attempt_status.py b/src/opencode/types/shared/integration_command_attempt_status.py
new file mode 100644
index 0000000..e55d50c
--- /dev/null
+++ b/src/opencode/types/shared/integration_command_attempt_status.py
@@ -0,0 +1,176 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union, Optional
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = [
+ "IntegrationCommandAttemptStatus",
+ "IntegrationCommandAttemptStatusVariant0",
+ "IntegrationCommandAttemptStatusVariant0Time",
+ "IntegrationCommandAttemptStatusVariant1",
+ "IntegrationCommandAttemptStatusVariant1Time",
+ "IntegrationCommandAttemptStatusVariant2",
+ "IntegrationCommandAttemptStatusVariant2Time",
+ "IntegrationCommandAttemptStatusVariant3",
+ "IntegrationCommandAttemptStatusVariant3Time",
+]
+
+
+class IntegrationCommandAttemptStatusVariant0Time(BaseModel):
+ created: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ expires: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class IntegrationCommandAttemptStatusVariant0(BaseModel):
+ status: Literal["pending"]
+
+ time: IntegrationCommandAttemptStatusVariant0Time
+
+ message: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class IntegrationCommandAttemptStatusVariant1Time(BaseModel):
+ created: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ expires: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class IntegrationCommandAttemptStatusVariant1(BaseModel):
+ status: Literal["complete"]
+
+ time: IntegrationCommandAttemptStatusVariant1Time
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class IntegrationCommandAttemptStatusVariant2Time(BaseModel):
+ created: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ expires: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class IntegrationCommandAttemptStatusVariant2(BaseModel):
+ message: str
+
+ status: Literal["failed"]
+
+ time: IntegrationCommandAttemptStatusVariant2Time
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class IntegrationCommandAttemptStatusVariant3Time(BaseModel):
+ created: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ expires: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class IntegrationCommandAttemptStatusVariant3(BaseModel):
+ status: Literal["expired"]
+
+ time: IntegrationCommandAttemptStatusVariant3Time
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+IntegrationCommandAttemptStatus: TypeAlias = Union[
+ IntegrationCommandAttemptStatusVariant0,
+ IntegrationCommandAttemptStatusVariant1,
+ IntegrationCommandAttemptStatusVariant2,
+ IntegrationCommandAttemptStatusVariant3,
+]
diff --git a/src/opencode/types/shared/integration_command_method.py b/src/opencode/types/shared/integration_command_method.py
new file mode 100644
index 0000000..1ac3162
--- /dev/null
+++ b/src/opencode/types/shared/integration_command_method.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .integration_method_id import IntegrationMethodID
+
+__all__ = ["IntegrationCommandMethod"]
+
+
+class IntegrationCommandMethod(BaseModel):
+ id: IntegrationMethodID
+
+ command: List[str]
+
+ label: str
+
+ type: Literal["command"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/integration_env_method.py b/src/opencode/types/shared/integration_env_method.py
new file mode 100644
index 0000000..309f0c6
--- /dev/null
+++ b/src/opencode/types/shared/integration_env_method.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["IntegrationEnvMethod"]
+
+
+class IntegrationEnvMethod(BaseModel):
+ names: List[str]
+
+ type: Literal["env"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/integration_external_method.py b/src/opencode/types/shared/integration_external_method.py
new file mode 100644
index 0000000..5cadbfc
--- /dev/null
+++ b/src/opencode/types/shared/integration_external_method.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .form_fields3 import FormFields3
+from .integration_method_id import IntegrationMethodID
+
+__all__ = ["IntegrationExternalMethod"]
+
+
+class IntegrationExternalMethod(BaseModel):
+ id: IntegrationMethodID
+
+ label: str
+
+ type: Literal["external"]
+
+ form: Optional[FormFields3] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/integration_id.py b/src/opencode/types/shared/integration_id.py
new file mode 100644
index 0000000..870af38
--- /dev/null
+++ b/src/opencode/types/shared/integration_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["IntegrationID"]
+
+IntegrationID: TypeAlias = str
diff --git a/src/opencode/types/shared/integration_info.py b/src/opencode/types/shared/integration_info.py
new file mode 100644
index 0000000..b4e0b12
--- /dev/null
+++ b/src/opencode/types/shared/integration_info.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .integration_id import IntegrationID
+from .connection_info import ConnectionInfo
+from .integration_method import IntegrationMethod
+
+__all__ = ["IntegrationInfo"]
+
+
+class IntegrationInfo(BaseModel):
+ id: IntegrationID
+
+ connections: List[ConnectionInfo]
+
+ methods: List[IntegrationMethod]
+
+ name: str
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/integration_key_method.py b/src/opencode/types/shared/integration_key_method.py
new file mode 100644
index 0000000..f9ee625
--- /dev/null
+++ b/src/opencode/types/shared/integration_key_method.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .form_fields2 import FormFields2
+
+__all__ = ["IntegrationKeyMethod"]
+
+
+class IntegrationKeyMethod(BaseModel):
+ type: Literal["key"]
+
+ form: Optional[FormFields2] = None
+
+ label: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/integration_method.py b/src/opencode/types/shared/integration_method.py
new file mode 100644
index 0000000..e1ec714
--- /dev/null
+++ b/src/opencode/types/shared/integration_method.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import TypeAlias
+
+from .integration_env_method import IntegrationEnvMethod
+from .integration_key_method import IntegrationKeyMethod
+from .integration_o_auth_method import IntegrationOAuthMethod
+from .integration_command_method import IntegrationCommandMethod
+from .integration_external_method import IntegrationExternalMethod
+
+__all__ = ["IntegrationMethod"]
+
+IntegrationMethod: TypeAlias = Union[
+ IntegrationOAuthMethod,
+ IntegrationCommandMethod,
+ IntegrationKeyMethod,
+ IntegrationExternalMethod,
+ IntegrationEnvMethod,
+]
diff --git a/src/opencode/types/shared/integration_method_id.py b/src/opencode/types/shared/integration_method_id.py
new file mode 100644
index 0000000..a851eb9
--- /dev/null
+++ b/src/opencode/types/shared/integration_method_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["IntegrationMethodID"]
+
+IntegrationMethodID: TypeAlias = str
diff --git a/src/opencode/types/shared/integration_o_auth_method.py b/src/opencode/types/shared/integration_o_auth_method.py
new file mode 100644
index 0000000..c27c375
--- /dev/null
+++ b/src/opencode/types/shared/integration_o_auth_method.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .form_fields1 import FormFields1
+from .integration_method_id import IntegrationMethodID
+
+__all__ = ["IntegrationOAuthMethod"]
+
+
+class IntegrationOAuthMethod(BaseModel):
+ id: IntegrationMethodID
+
+ label: str
+
+ type: Literal["oauth"]
+
+ form: Optional[FormFields1] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/integration_updated_event.py b/src/opencode/types/shared/integration_updated_event.py
new file mode 100644
index 0000000..4835396
--- /dev/null
+++ b/src/opencode/types/shared/integration_updated_event.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["IntegrationUpdatedEvent"]
+
+
+class IntegrationUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: object
+
+ type: Literal["integration.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/location_public_info.py b/src/opencode/types/shared/location_public_info.py
new file mode 100644
index 0000000..e1a4d6f
--- /dev/null
+++ b/src/opencode/types/shared/location_public_info.py
@@ -0,0 +1,49 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .project_id import ProjectID
+from .absolute_path import AbsolutePath
+
+__all__ = ["LocationPublicInfo", "Project"]
+
+
+class Project(BaseModel):
+ id: ProjectID
+
+ canonical: AbsolutePath
+
+ directory: AbsolutePath
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class LocationPublicInfo(BaseModel):
+ directory: AbsolutePath
+
+ project: Project
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/location_public_ref.py b/src/opencode/types/shared/location_public_ref.py
new file mode 100644
index 0000000..f3c8499
--- /dev/null
+++ b/src/opencode/types/shared/location_public_ref.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .absolute_path import AbsolutePath
+
+__all__ = ["LocationPublicRef"]
+
+
+class LocationPublicRef(BaseModel):
+ directory: AbsolutePath
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/location_ref.py b/src/opencode/types/shared/location_ref.py
new file mode 100644
index 0000000..4956293
--- /dev/null
+++ b/src/opencode/types/shared/location_ref.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .workspace_id import WorkspaceID
+from .absolute_path import AbsolutePath
+
+__all__ = ["LocationRef"]
+
+
+class LocationRef(BaseModel):
+ directory: AbsolutePath
+
+ workspace_id: Optional[WorkspaceID] = FieldInfo(alias="workspaceID", default=None)
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/location_shutdown_event.py b/src/opencode/types/shared/location_shutdown_event.py
new file mode 100644
index 0000000..a188bf2
--- /dev/null
+++ b/src/opencode/types/shared/location_shutdown_event.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["LocationShutdownEvent"]
+
+
+class LocationShutdownEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: object
+
+ type: Literal["location.shutdown"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/mcp_local_config.py b/src/opencode/types/shared/mcp_local_config.py
new file mode 100644
index 0000000..84019f5
--- /dev/null
+++ b/src/opencode/types/shared/mcp_local_config.py
@@ -0,0 +1,68 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .mcp_protocol import McpProtocol
+
+__all__ = ["McpLocalConfig", "Timeout"]
+
+
+class Timeout(BaseModel):
+ catalog: Optional[int] = None
+
+ execution: Optional[int] = None
+
+ startup: Optional[int] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class McpLocalConfig(BaseModel):
+ command: List[str]
+
+ type: Literal["local"]
+
+ codemode: Optional[bool] = None
+
+ cwd: Optional[str] = None
+
+ disabled: Optional[bool] = None
+
+ environment: Optional[Dict[str, str]] = None
+
+ protocol: Optional[McpProtocol] = None
+ """MCP protocol negotiation.
+
+ "legacy" (default) opens with the initialize handshake and speaks protocol
+ revisions up to 2025-11-25. "auto" probes for the 2026-07-28 revision and falls
+ back to legacy when the server does not support it. "2026-07-28" requires that
+ revision and fails otherwise.
+ """
+
+ timeout: Optional[Timeout] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/mcp_o_auth_config.py b/src/opencode/types/shared/mcp_o_auth_config.py
new file mode 100644
index 0000000..a9f5108
--- /dev/null
+++ b/src/opencode/types/shared/mcp_o_auth_config.py
@@ -0,0 +1,35 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["McpOAuthConfig"]
+
+
+class McpOAuthConfig(BaseModel):
+ auth_server_metadata_url: Optional[str] = None
+
+ callback_port: Optional[int] = None
+
+ client_id: Optional[str] = None
+
+ client_secret: Optional[str] = None
+
+ redirect_uri: Optional[str] = None
+
+ scope: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/mcp_protocol.py b/src/opencode/types/shared/mcp_protocol.py
new file mode 100644
index 0000000..5bd5ac3
--- /dev/null
+++ b/src/opencode/types/shared/mcp_protocol.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["McpProtocol"]
+
+McpProtocol: TypeAlias = Literal["legacy", "auto", "2026-07-28"]
diff --git a/src/opencode/types/shared/mcp_remote_config.py b/src/opencode/types/shared/mcp_remote_config.py
new file mode 100644
index 0000000..3058984
--- /dev/null
+++ b/src/opencode/types/shared/mcp_remote_config.py
@@ -0,0 +1,71 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union, Optional
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .mcp_protocol import McpProtocol
+from .mcp_o_auth_config import McpOAuthConfig
+
+__all__ = ["McpRemoteConfig", "Oauth", "Timeout"]
+
+Oauth: TypeAlias = Union[Literal[False], McpOAuthConfig]
+
+
+class Timeout(BaseModel):
+ catalog: Optional[int] = None
+
+ execution: Optional[int] = None
+
+ startup: Optional[int] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class McpRemoteConfig(BaseModel):
+ type: Literal["remote"]
+
+ url: str
+
+ codemode: Optional[bool] = None
+
+ disabled: Optional[bool] = None
+
+ headers: Optional[Dict[str, str]] = None
+
+ oauth: Optional[Oauth] = None
+
+ protocol: Optional[McpProtocol] = None
+ """MCP protocol negotiation.
+
+ "legacy" (default) opens with the initialize handshake and speaks protocol
+ revisions up to 2025-11-25. "auto" probes for the 2026-07-28 revision and falls
+ back to legacy when the server does not support it. "2026-07-28" requires that
+ revision and fails otherwise.
+ """
+
+ timeout: Optional[Timeout] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/mcp_resource.py b/src/opencode/types/shared/mcp_resource.py
new file mode 100644
index 0000000..fe97a70
--- /dev/null
+++ b/src/opencode/types/shared/mcp_resource.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["McpResource"]
+
+
+class McpResource(BaseModel):
+ name: str
+
+ server: str
+
+ uri: str
+
+ description: Optional[str] = None
+
+ mime_type: Optional[str] = FieldInfo(alias="mimeType", default=None)
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/mcp_resource_catalog.py b/src/opencode/types/shared/mcp_resource_catalog.py
new file mode 100644
index 0000000..6f80338
--- /dev/null
+++ b/src/opencode/types/shared/mcp_resource_catalog.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .mcp_resource import McpResource
+from .mcp_resource_template import McpResourceTemplate
+
+__all__ = ["McpResourceCatalog"]
+
+
+class McpResourceCatalog(BaseModel):
+ resources: List[McpResource]
+
+ templates: List[McpResourceTemplate]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/mcp_resource_template.py b/src/opencode/types/shared/mcp_resource_template.py
new file mode 100644
index 0000000..643e5f6
--- /dev/null
+++ b/src/opencode/types/shared/mcp_resource_template.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["McpResourceTemplate"]
+
+
+class McpResourceTemplate(BaseModel):
+ name: str
+
+ server: str
+
+ uri_template: str = FieldInfo(alias="uriTemplate")
+
+ description: Optional[str] = None
+
+ mime_type: Optional[str] = FieldInfo(alias="mimeType", default=None)
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/mcp_resources_changed_event.py b/src/opencode/types/shared/mcp_resources_changed_event.py
new file mode 100644
index 0000000..28c4ee2
--- /dev/null
+++ b/src/opencode/types/shared/mcp_resources_changed_event.py
@@ -0,0 +1,54 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["McpResourcesChangedEvent", "Data"]
+
+
+class Data(BaseModel):
+ server: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class McpResourcesChangedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["mcp.resources.changed"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/mcp_server.py b/src/opencode/types/shared/mcp_server.py
new file mode 100644
index 0000000..7f6daad
--- /dev/null
+++ b/src/opencode/types/shared/mcp_server.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union, Optional
+from typing_extensions import TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .integration_id import IntegrationID
+from .mcp_status_failed import McpStatusFailed
+from .mcp_status_pending import McpStatusPending
+from .mcp_status_disabled import McpStatusDisabled
+from .mcp_status_connected import McpStatusConnected
+from .mcp_status_needs_auth import McpStatusNeedsAuth
+
+__all__ = ["McpServer", "Status"]
+
+Status: TypeAlias = Union[McpStatusConnected, McpStatusPending, McpStatusDisabled, McpStatusFailed, McpStatusNeedsAuth]
+
+
+class McpServer(BaseModel):
+ name: str
+
+ status: Status
+
+ integration_id: Optional[IntegrationID] = FieldInfo(alias="integrationID", default=None)
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/mcp_status_changed_event.py b/src/opencode/types/shared/mcp_status_changed_event.py
new file mode 100644
index 0000000..57550bb
--- /dev/null
+++ b/src/opencode/types/shared/mcp_status_changed_event.py
@@ -0,0 +1,54 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["McpStatusChangedEvent", "Data"]
+
+
+class Data(BaseModel):
+ server: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class McpStatusChangedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["mcp.status.changed"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/mcp_status_connected.py b/src/opencode/types/shared/mcp_status_connected.py
new file mode 100644
index 0000000..4f3d237
--- /dev/null
+++ b/src/opencode/types/shared/mcp_status_connected.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["McpStatusConnected"]
+
+
+class McpStatusConnected(BaseModel):
+ status: Literal["connected"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/mcp_status_disabled.py b/src/opencode/types/shared/mcp_status_disabled.py
new file mode 100644
index 0000000..a2e5941
--- /dev/null
+++ b/src/opencode/types/shared/mcp_status_disabled.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["McpStatusDisabled"]
+
+
+class McpStatusDisabled(BaseModel):
+ status: Literal["disabled"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/mcp_status_failed.py b/src/opencode/types/shared/mcp_status_failed.py
new file mode 100644
index 0000000..c629880
--- /dev/null
+++ b/src/opencode/types/shared/mcp_status_failed.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["McpStatusFailed"]
+
+
+class McpStatusFailed(BaseModel):
+ error: str
+
+ status: Literal["failed"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/mcp_status_needs_auth.py b/src/opencode/types/shared/mcp_status_needs_auth.py
new file mode 100644
index 0000000..5868113
--- /dev/null
+++ b/src/opencode/types/shared/mcp_status_needs_auth.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["McpStatusNeedsAuth"]
+
+
+class McpStatusNeedsAuth(BaseModel):
+ error: str
+
+ status: Literal["needs_auth"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/mcp_status_pending.py b/src/opencode/types/shared/mcp_status_pending.py
new file mode 100644
index 0000000..edfc166
--- /dev/null
+++ b/src/opencode/types/shared/mcp_status_pending.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["McpStatusPending"]
+
+
+class McpStatusPending(BaseModel):
+ status: Literal["pending"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/model_cache_format.py b/src/opencode/types/shared/model_cache_format.py
new file mode 100644
index 0000000..83fa1b7
--- /dev/null
+++ b/src/opencode/types/shared/model_cache_format.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["ModelCacheFormat"]
+
+ModelCacheFormat: TypeAlias = Literal["anthropic", "alibaba"]
diff --git a/src/opencode/types/shared/model_capabilities.py b/src/opencode/types/shared/model_capabilities.py
new file mode 100644
index 0000000..dd9f7d2
--- /dev/null
+++ b/src/opencode/types/shared/model_capabilities.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ModelCapabilities"]
+
+
+class ModelCapabilities(BaseModel):
+ input: List[str]
+
+ output: List[str]
+
+ tools: bool
+
+ temperature: Optional[bool] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/model_compatibility.py b/src/opencode/types/shared/model_compatibility.py
new file mode 100644
index 0000000..b1aac57
--- /dev/null
+++ b/src/opencode/types/shared/model_compatibility.py
@@ -0,0 +1,46 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .model_cache_format import ModelCacheFormat
+from .model_reasoning_field import ModelReasoningField
+from .model_max_tokens_field import ModelMaxTokensField
+
+__all__ = ["ModelCompatibility"]
+
+
+class ModelCompatibility(BaseModel):
+ cache_format: Optional[ModelCacheFormat] = FieldInfo(alias="cacheFormat", default=None)
+
+ max_tokens_field: Optional[ModelMaxTokensField] = FieldInfo(alias="maxTokensField", default=None)
+
+ reasoning_field: Optional[ModelReasoningField] = FieldInfo(alias="reasoningField", default=None)
+
+ require_assistant_after_tool: Optional[bool] = FieldInfo(alias="requireAssistantAfterTool", default=None)
+
+ require_finish_reason: Optional[bool] = FieldInfo(alias="requireFinishReason", default=None)
+
+ require_reasoning: Optional[bool] = FieldInfo(alias="requireReasoning", default=None)
+
+ stream_usage: Optional[bool] = FieldInfo(alias="streamUsage", default=None)
+
+ supports_effort_updates: Optional[bool] = FieldInfo(alias="supportsEffortUpdates", default=None)
+
+ supports_prompt_cache_key: Optional[bool] = FieldInfo(alias="supportsPromptCacheKey", default=None)
+
+ supports_thinking_block_binding: Optional[bool] = FieldInfo(alias="supportsThinkingBlockBinding", default=None)
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/model_cost.py b/src/opencode/types/shared/model_cost.py
new file mode 100644
index 0000000..477930f
--- /dev/null
+++ b/src/opencode/types/shared/model_cost.py
@@ -0,0 +1,69 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .money_usd_per_million_tokens import MoneyUSDPerMillionTokens
+
+__all__ = ["ModelCost", "Cache", "Tier"]
+
+
+class Cache(BaseModel):
+ read: MoneyUSDPerMillionTokens
+
+ write: MoneyUSDPerMillionTokens
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Tier(BaseModel):
+ size: int
+
+ type: Literal["context"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class ModelCost(BaseModel):
+ cache: Cache
+
+ input: MoneyUSDPerMillionTokens
+
+ output: MoneyUSDPerMillionTokens
+
+ tier: Optional[Tier] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/model_info.py b/src/opencode/types/shared/model_info.py
new file mode 100644
index 0000000..17ec69a
--- /dev/null
+++ b/src/opencode/types/shared/model_info.py
@@ -0,0 +1,102 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .model_cost import ModelCost
+from .provider_id import ProviderID
+from .model_variant import ModelVariant
+from .model_settings import ModelSettings
+from .model_capabilities import ModelCapabilities
+from .model_compatibility import ModelCompatibility
+
+__all__ = ["ModelInfo", "Limit", "Time"]
+
+
+class Limit(BaseModel):
+ context: int
+
+ output: int
+
+ input: Optional[int] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Time(BaseModel):
+ released: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class ModelInfo(BaseModel):
+ id: str
+
+ capabilities: ModelCapabilities
+
+ cost: List[ModelCost]
+
+ enabled: bool
+
+ limit: Limit
+
+ api_model_id: str = FieldInfo(alias="modelID")
+
+ name: str
+
+ provider_id: ProviderID = FieldInfo(alias="providerID")
+
+ status: Literal["alpha", "beta", "deprecated", "active"]
+
+ time: Time
+
+ variants: List[ModelVariant]
+
+ body: Optional[Dict[str, object]] = None
+
+ canonical: Optional[ProviderID] = None
+
+ compatibility: Optional[ModelCompatibility] = None
+
+ family: Optional[str] = None
+
+ headers: Optional[Dict[str, str]] = None
+
+ package: Optional[str] = None
+
+ settings: Optional[ModelSettings] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/model_max_tokens_field.py b/src/opencode/types/shared/model_max_tokens_field.py
new file mode 100644
index 0000000..cf6bb14
--- /dev/null
+++ b/src/opencode/types/shared/model_max_tokens_field.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["ModelMaxTokensField"]
+
+ModelMaxTokensField: TypeAlias = Literal["max_completion_tokens", "max_tokens"]
diff --git a/src/opencode/types/shared/model_reasoning_field.py b/src/opencode/types/shared/model_reasoning_field.py
new file mode 100644
index 0000000..32b4436
--- /dev/null
+++ b/src/opencode/types/shared/model_reasoning_field.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["ModelReasoningField"]
+
+ModelReasoningField: TypeAlias = Union[Literal["reasoning", "reasoning_content", "reasoning_text"], str]
diff --git a/src/opencode/types/shared/model_ref.py b/src/opencode/types/shared/model_ref.py
new file mode 100644
index 0000000..532e06f
--- /dev/null
+++ b/src/opencode/types/shared/model_ref.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .provider_id import ProviderID
+
+__all__ = ["ModelRef"]
+
+
+class ModelRef(BaseModel):
+ id: str
+
+ provider_id: ProviderID = FieldInfo(alias="providerID")
+
+ variant: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/model_settings.py b/src/opencode/types/shared/model_settings.py
new file mode 100644
index 0000000..099581e
--- /dev/null
+++ b/src/opencode/types/shared/model_settings.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .provider_encoding import ProviderEncoding
+from .provider_compaction import ProviderCompaction
+
+__all__ = ["ModelSettings"]
+
+
+class ModelSettings(BaseModel):
+ compaction: Optional[ProviderCompaction] = None
+
+ encoding: Optional[ProviderEncoding] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/model_updated_event.py b/src/opencode/types/shared/model_updated_event.py
new file mode 100644
index 0000000..35e7a81
--- /dev/null
+++ b/src/opencode/types/shared/model_updated_event.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["ModelUpdatedEvent"]
+
+
+class ModelUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: object
+
+ type: Literal["model.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/model_variant.py b/src/opencode/types/shared/model_variant.py
new file mode 100644
index 0000000..9b1a5d2
--- /dev/null
+++ b/src/opencode/types/shared/model_variant.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .model_settings import ModelSettings
+
+__all__ = ["ModelVariant"]
+
+
+class ModelVariant(BaseModel):
+ id: str
+
+ body: Optional[Dict[str, object]] = None
+
+ headers: Optional[Dict[str, str]] = None
+
+ settings: Optional[ModelSettings] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/models_dev_refreshed_event.py b/src/opencode/types/shared/models_dev_refreshed_event.py
new file mode 100644
index 0000000..a30c5bf
--- /dev/null
+++ b/src/opencode/types/shared/models_dev_refreshed_event.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["ModelsDevRefreshedEvent"]
+
+
+class ModelsDevRefreshedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: object
+
+ type: Literal["models-dev.refreshed"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/money_usd.py b/src/opencode/types/shared/money_usd.py
new file mode 100644
index 0000000..36f0f98
--- /dev/null
+++ b/src/opencode/types/shared/money_usd.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["MoneyUSD"]
+
+MoneyUSD: TypeAlias = float
diff --git a/src/opencode/types/shared/money_usd_per_million_tokens.py b/src/opencode/types/shared/money_usd_per_million_tokens.py
new file mode 100644
index 0000000..99eae68
--- /dev/null
+++ b/src/opencode/types/shared/money_usd_per_million_tokens.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["MoneyUSDPerMillionTokens"]
+
+MoneyUSDPerMillionTokens: TypeAlias = float
diff --git a/src/opencode/types/shared/pairing_code.py b/src/opencode/types/shared/pairing_code.py
new file mode 100644
index 0000000..46611bb
--- /dev/null
+++ b/src/opencode/types/shared/pairing_code.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["PairingCode"]
+
+
+class PairingCode(BaseModel):
+ code: str
+
+ expires_in: int
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/pairing_session.py b/src/opencode/types/shared/pairing_session.py
new file mode 100644
index 0000000..9c3a0c1
--- /dev/null
+++ b/src/opencode/types/shared/pairing_session.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["PairingSession"]
+
+
+class PairingSession(BaseModel):
+ token: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/permission_asked_event.py b/src/opencode/types/shared/permission_asked_event.py
new file mode 100644
index 0000000..875be8b
--- /dev/null
+++ b/src/opencode/types/shared/permission_asked_event.py
@@ -0,0 +1,71 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .permission_id import PermissionID
+from .permission_source import PermissionSource
+
+__all__ = ["PermissionAskedEvent", "Data"]
+
+
+class Data(BaseModel):
+ id: PermissionID
+
+ action: str
+
+ resources: List[str]
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ message: Optional[str] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ save: Optional[List[str]] = None
+
+ source: Optional[PermissionSource] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class PermissionAskedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["permission.asked"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/permission_effect.py b/src/opencode/types/shared/permission_effect.py
new file mode 100644
index 0000000..d509f39
--- /dev/null
+++ b/src/opencode/types/shared/permission_effect.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["PermissionEffect"]
+
+PermissionEffect: TypeAlias = Literal["allow", "deny", "ask"]
diff --git a/src/opencode/types/shared/permission_id.py b/src/opencode/types/shared/permission_id.py
new file mode 100644
index 0000000..9b08d92
--- /dev/null
+++ b/src/opencode/types/shared/permission_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["PermissionID"]
+
+PermissionID: TypeAlias = str
diff --git a/src/opencode/types/shared/permission_replied_event.py b/src/opencode/types/shared/permission_replied_event.py
new file mode 100644
index 0000000..e5b5620
--- /dev/null
+++ b/src/opencode/types/shared/permission_replied_event.py
@@ -0,0 +1,61 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .permission_id import PermissionID
+from .permission_reply import PermissionReply
+
+__all__ = ["PermissionRepliedEvent", "Data"]
+
+
+class Data(BaseModel):
+ reply: PermissionReply
+
+ request_id: PermissionID = FieldInfo(alias="requestID")
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class PermissionRepliedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["permission.replied"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/permission_reply.py b/src/opencode/types/shared/permission_reply.py
new file mode 100644
index 0000000..7f84759
--- /dev/null
+++ b/src/opencode/types/shared/permission_reply.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["PermissionReply"]
+
+PermissionReply: TypeAlias = Literal["once", "always", "reject"]
diff --git a/src/opencode/types/shared/permission_request.py b/src/opencode/types/shared/permission_request.py
new file mode 100644
index 0000000..1543bc3
--- /dev/null
+++ b/src/opencode/types/shared/permission_request.py
@@ -0,0 +1,42 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_id import SessionID
+from .permission_id import PermissionID
+from .permission_source import PermissionSource
+
+__all__ = ["PermissionRequest"]
+
+
+class PermissionRequest(BaseModel):
+ id: PermissionID
+
+ action: str
+
+ resources: List[str]
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ message: Optional[str] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ save: Optional[List[str]] = None
+
+ source: Optional[PermissionSource] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/permission_rule.py b/src/opencode/types/shared/permission_rule.py
new file mode 100644
index 0000000..c87435f
--- /dev/null
+++ b/src/opencode/types/shared/permission_rule.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .permission_effect import PermissionEffect
+
+__all__ = ["PermissionRule"]
+
+
+class PermissionRule(BaseModel):
+ action: str
+
+ effect: PermissionEffect
+
+ resource: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/permission_ruleset.py b/src/opencode/types/shared/permission_ruleset.py
new file mode 100644
index 0000000..0db09c8
--- /dev/null
+++ b/src/opencode/types/shared/permission_ruleset.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+from .permission_rule import PermissionRule
+
+__all__ = ["PermissionRuleset"]
+
+PermissionRuleset: TypeAlias = List[PermissionRule]
diff --git a/src/opencode/types/shared/permission_saved_id.py b/src/opencode/types/shared/permission_saved_id.py
new file mode 100644
index 0000000..25ff2d9
--- /dev/null
+++ b/src/opencode/types/shared/permission_saved_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["PermissionSavedID"]
+
+PermissionSavedID: TypeAlias = str
diff --git a/src/opencode/types/shared/permission_saved_info.py b/src/opencode/types/shared/permission_saved_info.py
new file mode 100644
index 0000000..0fa3275
--- /dev/null
+++ b/src/opencode/types/shared/permission_saved_info.py
@@ -0,0 +1,53 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .project_id import ProjectID
+from .permission_saved_id import PermissionSavedID
+
+__all__ = ["PermissionSavedInfo", "Time"]
+
+
+class Time(BaseModel):
+ created: float
+
+ updated: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class PermissionSavedInfo(BaseModel):
+ id: PermissionSavedID
+
+ action: str
+
+ project_id: ProjectID = FieldInfo(alias="projectID")
+
+ resource: str
+
+ time: Time
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/permission_source.py b/src/opencode/types/shared/permission_source.py
new file mode 100644
index 0000000..8d2e58d
--- /dev/null
+++ b/src/opencode/types/shared/permission_source.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["PermissionSource"]
+
+
+class PermissionSource(BaseModel):
+ id: str
+
+ message_id: str = FieldInfo(alias="messageID")
+
+ type: Literal["tool"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/persistent_pty_added_event.py b/src/opencode/types/shared/persistent_pty_added_event.py
new file mode 100644
index 0000000..9080015
--- /dev/null
+++ b/src/opencode/types/shared/persistent_pty_added_event.py
@@ -0,0 +1,58 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .persistent_pty_info import PersistentPtyInfo
+
+__all__ = ["PersistentPtyAddedEvent", "Data"]
+
+
+class Data(BaseModel):
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ terminal: PersistentPtyInfo
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class PersistentPtyAddedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["persistent-pty.added"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/persistent_pty_handoff.py b/src/opencode/types/shared/persistent_pty_handoff.py
new file mode 100644
index 0000000..0a83aba
--- /dev/null
+++ b/src/opencode/types/shared/persistent_pty_handoff.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["PersistentPtyHandoff"]
+
+
+class PersistentPtyHandoff(BaseModel):
+ directory: str
+
+ expires_at: Union[Literal["Infinity", "-Infinity", "NaN"], float] = FieldInfo(alias="expiresAt")
+
+ instance_id: str = FieldInfo(alias="instanceID")
+
+ ticket: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/persistent_pty_info.py b/src/opencode/types/shared/persistent_pty_info.py
new file mode 100644
index 0000000..b630450
--- /dev/null
+++ b/src/opencode/types/shared/persistent_pty_info.py
@@ -0,0 +1,86 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .pty_id import PtyID
+from ..._models import BaseModel
+from .session_id import SessionID
+
+__all__ = ["PersistentPtyInfo", "Output", "Size"]
+
+
+class Output(BaseModel):
+ head: int
+
+ tail: int
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Size(BaseModel):
+ cols: int
+
+ rows: int
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class PersistentPtyInfo(BaseModel):
+ id: PtyID
+
+ args: List[str]
+
+ command: str
+
+ cwd: str
+
+ foreground_process: Optional[str] = FieldInfo(alias="foregroundProcess", default=None)
+
+ output: Output
+
+ pid: int
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ size: Size
+
+ status: Literal["running", "exited"]
+
+ title: str
+
+ exit_code: Optional[int] = FieldInfo(alias="exitCode", default=None)
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/persistent_pty_read_lines.py b/src/opencode/types/shared/persistent_pty_read_lines.py
new file mode 100644
index 0000000..5cfdb59
--- /dev/null
+++ b/src/opencode/types/shared/persistent_pty_read_lines.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["PersistentPtyReadLines"]
+
+PersistentPtyReadLines: TypeAlias = int
diff --git a/src/opencode/types/shared/persistent_pty_read_result.py b/src/opencode/types/shared/persistent_pty_read_result.py
new file mode 100644
index 0000000..a143d96
--- /dev/null
+++ b/src/opencode/types/shared/persistent_pty_read_result.py
@@ -0,0 +1,74 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from .pty_id import PtyID
+from ..._models import BaseModel
+
+__all__ = ["PersistentPtyReadResult", "Screen", "ScreenCursor"]
+
+
+class ScreenCursor(BaseModel):
+ x: int
+
+ y: int
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Screen(BaseModel):
+ cols: int
+
+ cursor: ScreenCursor
+
+ rows: int
+
+ text: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class PersistentPtyReadResult(BaseModel):
+ cwd: str
+
+ foreground_process: Optional[str] = FieldInfo(alias="foregroundProcess", default=None)
+
+ pty_id: PtyID = FieldInfo(alias="ptyID")
+
+ screen: Screen
+
+ title: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/persistent_pty_removed_event.py b/src/opencode/types/shared/persistent_pty_removed_event.py
new file mode 100644
index 0000000..124974d
--- /dev/null
+++ b/src/opencode/types/shared/persistent_pty_removed_event.py
@@ -0,0 +1,58 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .pty_id import PtyID
+from .event_id import EventID
+from ..._models import BaseModel
+from .session_id import SessionID
+from .location_ref import LocationRef
+
+__all__ = ["PersistentPtyRemovedEvent", "Data"]
+
+
+class Data(BaseModel):
+ pty_id: PtyID = FieldInfo(alias="ptyID")
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class PersistentPtyRemovedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["persistent-pty.removed"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/persistent_pty_snapshot.py b/src/opencode/types/shared/persistent_pty_snapshot.py
new file mode 100644
index 0000000..2a3a18a
--- /dev/null
+++ b/src/opencode/types/shared/persistent_pty_snapshot.py
@@ -0,0 +1,50 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .persistent_pty_info import PersistentPtyInfo
+
+__all__ = ["PersistentPtySnapshot", "Cursor"]
+
+
+class Cursor(BaseModel):
+ x: int
+
+ y: int
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class PersistentPtySnapshot(BaseModel):
+ checkpoint: str
+
+ cursor: Cursor
+
+ info: PersistentPtyInfo
+
+ text: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/plugin_features.py b/src/opencode/types/shared/plugin_features.py
new file mode 100644
index 0000000..e899378
--- /dev/null
+++ b/src/opencode/types/shared/plugin_features.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["PluginFeatures"]
+
+
+class PluginFeatures(BaseModel):
+ rpc: Optional[Literal[True]] = None
+
+ server: Optional[Literal[True]] = None
+
+ tui: Optional[Literal[True]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/plugin_info.py b/src/opencode/types/shared/plugin_info.py
new file mode 100644
index 0000000..402f60f
--- /dev/null
+++ b/src/opencode/types/shared/plugin_info.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .plugin_state import PluginState
+from .plugin_source import PluginSource
+from .plugin_features import PluginFeatures
+
+__all__ = ["PluginInfo"]
+
+
+class PluginInfo(BaseModel):
+ features: PluginFeatures
+
+ source: PluginSource
+
+ state: PluginState
+
+ id: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/plugin_source.py b/src/opencode/types/shared/plugin_source.py
new file mode 100644
index 0000000..c83e5ac
--- /dev/null
+++ b/src/opencode/types/shared/plugin_source.py
@@ -0,0 +1,93 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union, Optional
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = [
+ "PluginSource",
+ "PluginSourceVariant0",
+ "PluginSourceVariant1",
+ "PluginSourceVariant2",
+ "PluginSourceVariant3",
+]
+
+
+class PluginSourceVariant0(BaseModel):
+ type: Literal["builtin"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class PluginSourceVariant1(BaseModel):
+ target: str
+
+ type: Literal["package"]
+
+ outdated: Optional[Literal[True]] = None
+
+ updating: Optional[Literal[True]] = None
+
+ version: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class PluginSourceVariant2(BaseModel):
+ path: str
+
+ type: Literal["local"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class PluginSourceVariant3(BaseModel):
+ type: Literal["sdk"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+PluginSource: TypeAlias = Union[PluginSourceVariant0, PluginSourceVariant1, PluginSourceVariant2, PluginSourceVariant3]
diff --git a/src/opencode/types/shared/plugin_state.py b/src/opencode/types/shared/plugin_state.py
new file mode 100644
index 0000000..298c71d
--- /dev/null
+++ b/src/opencode/types/shared/plugin_state.py
@@ -0,0 +1,49 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union, Optional
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["PluginState", "PluginStateVariant0", "PluginStateVariant1"]
+
+
+class PluginStateVariant0(BaseModel):
+ status: Literal["active"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class PluginStateVariant1(BaseModel):
+ error: str
+
+ status: Literal["failed"]
+
+ ref: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+PluginState: TypeAlias = Union[PluginStateVariant0, PluginStateVariant1]
diff --git a/src/opencode/types/shared/plugin_updated_event.py b/src/opencode/types/shared/plugin_updated_event.py
new file mode 100644
index 0000000..509288b
--- /dev/null
+++ b/src/opencode/types/shared/plugin_updated_event.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["PluginUpdatedEvent"]
+
+
+class PluginUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: object
+
+ type: Literal["plugin.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/project.py b/src/opencode/types/shared/project.py
new file mode 100644
index 0000000..b0354be
--- /dev/null
+++ b/src/opencode/types/shared/project.py
@@ -0,0 +1,45 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .project_id import ProjectID
+from .project_vcs import ProjectVcs
+from .project_icon import ProjectIcon
+from .project_time import ProjectTime
+from .absolute_path import AbsolutePath
+from .project_commands import ProjectCommands
+
+__all__ = ["Project"]
+
+
+class Project(BaseModel):
+ id: ProjectID
+
+ canonical: AbsolutePath
+
+ sandboxes: List[str]
+
+ time: ProjectTime
+
+ commands: Optional[ProjectCommands] = None
+
+ icon: Optional[ProjectIcon] = None
+
+ name: Optional[str] = None
+
+ vcs: Optional[ProjectVcs] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/project_commands.py b/src/opencode/types/shared/project_commands.py
new file mode 100644
index 0000000..406c711
--- /dev/null
+++ b/src/opencode/types/shared/project_commands.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ProjectCommands"]
+
+
+class ProjectCommands(BaseModel):
+ start: Optional[str] = None
+ """Startup script to run when creating a new workspace (worktree)"""
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/project_icon.py b/src/opencode/types/shared/project_icon.py
new file mode 100644
index 0000000..fd90cc0
--- /dev/null
+++ b/src/opencode/types/shared/project_icon.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ProjectIcon"]
+
+
+class ProjectIcon(BaseModel):
+ color: Optional[str] = None
+
+ override: Optional[str] = None
+
+ url: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/project_id.py b/src/opencode/types/shared/project_id.py
new file mode 100644
index 0000000..ca48edd
--- /dev/null
+++ b/src/opencode/types/shared/project_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["ProjectID"]
+
+ProjectID: TypeAlias = str
diff --git a/src/opencode/types/shared/project_time.py b/src/opencode/types/shared/project_time.py
new file mode 100644
index 0000000..2e9526e
--- /dev/null
+++ b/src/opencode/types/shared/project_time.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ProjectTime"]
+
+
+class ProjectTime(BaseModel):
+ active: int
+
+ created: int
+
+ updated: int
+
+ archived: Optional[int] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/project_updated_event.py b/src/opencode/types/shared/project_updated_event.py
new file mode 100644
index 0000000..2b70e81
--- /dev/null
+++ b/src/opencode/types/shared/project_updated_event.py
@@ -0,0 +1,74 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .project_id import ProjectID
+from .project_vcs import ProjectVcs
+from .location_ref import LocationRef
+from .project_icon import ProjectIcon
+from .project_time import ProjectTime
+from .absolute_path import AbsolutePath
+from .project_commands import ProjectCommands
+
+__all__ = ["ProjectUpdatedEvent", "Data"]
+
+
+class Data(BaseModel):
+ id: ProjectID
+
+ canonical: AbsolutePath
+
+ sandboxes: List[str]
+
+ time: ProjectTime
+
+ commands: Optional[ProjectCommands] = None
+
+ icon: Optional[ProjectIcon] = None
+
+ name: Optional[str] = None
+
+ vcs: Optional[ProjectVcs] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class ProjectUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["project.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/project_vcs.py b/src/opencode/types/shared/project_vcs.py
new file mode 100644
index 0000000..af1e0ea
--- /dev/null
+++ b/src/opencode/types/shared/project_vcs.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["ProjectVcs"]
+
+ProjectVcs: TypeAlias = str
diff --git a/src/opencode/types/shared/prompt_agent_attachment.py b/src/opencode/types/shared/prompt_agent_attachment.py
new file mode 100644
index 0000000..b8b4959
--- /dev/null
+++ b/src/opencode/types/shared/prompt_agent_attachment.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .prompt_mention import PromptMention
+
+__all__ = ["PromptAgentAttachment"]
+
+
+class PromptAgentAttachment(BaseModel):
+ name: str
+
+ mention: Optional[PromptMention] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/prompt_base64.py b/src/opencode/types/shared/prompt_base64.py
new file mode 100644
index 0000000..b3e77c2
--- /dev/null
+++ b/src/opencode/types/shared/prompt_base64.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["PromptBase64"]
+
+PromptBase64: TypeAlias = str
diff --git a/src/opencode/types/shared/prompt_file_attachment.py b/src/opencode/types/shared/prompt_file_attachment.py
new file mode 100644
index 0000000..ac93158
--- /dev/null
+++ b/src/opencode/types/shared/prompt_file_attachment.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .prompt_base64 import PromptBase64
+from .prompt_mention import PromptMention
+from .prompt_file_source import PromptFileSource
+
+__all__ = ["PromptFileAttachment"]
+
+
+class PromptFileAttachment(BaseModel):
+ data: PromptBase64
+
+ mime: str
+
+ source: PromptFileSource
+
+ description: Optional[str] = None
+
+ mention: Optional[PromptMention] = None
+
+ name: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/prompt_file_source.py b/src/opencode/types/shared/prompt_file_source.py
new file mode 100644
index 0000000..af8d82c
--- /dev/null
+++ b/src/opencode/types/shared/prompt_file_source.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["PromptFileSource", "PromptFileSourceVariant0", "PromptFileSourceVariant1"]
+
+
+class PromptFileSourceVariant0(BaseModel):
+ type: Literal["inline"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class PromptFileSourceVariant1(BaseModel):
+ type: Literal["uri"]
+
+ uri: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+PromptFileSource: TypeAlias = Union[PromptFileSourceVariant0, PromptFileSourceVariant1]
diff --git a/src/opencode/types/shared/prompt_input_file_attachment.py b/src/opencode/types/shared/prompt_input_file_attachment.py
new file mode 100644
index 0000000..dd063e4
--- /dev/null
+++ b/src/opencode/types/shared/prompt_input_file_attachment.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .prompt_mention import PromptMention
+
+__all__ = ["PromptInputFileAttachment"]
+
+
+class PromptInputFileAttachment(BaseModel):
+ uri: str
+
+ description: Optional[str] = None
+
+ mention: Optional[PromptMention] = None
+
+ name: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/prompt_input_skill_attachment.py b/src/opencode/types/shared/prompt_input_skill_attachment.py
new file mode 100644
index 0000000..0e815d4
--- /dev/null
+++ b/src/opencode/types/shared/prompt_input_skill_attachment.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from .skill_id import SkillID
+from ..._models import BaseModel
+from .prompt_mention import PromptMention
+
+__all__ = ["PromptInputSkillAttachment"]
+
+
+class PromptInputSkillAttachment(BaseModel):
+ id: SkillID
+
+ mention: Optional[PromptMention] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/prompt_mention.py b/src/opencode/types/shared/prompt_mention.py
new file mode 100644
index 0000000..d00f205
--- /dev/null
+++ b/src/opencode/types/shared/prompt_mention.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["PromptMention"]
+
+
+class PromptMention(BaseModel):
+ end: float
+
+ start: float
+
+ text: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/prompt_skill_attachment.py b/src/opencode/types/shared/prompt_skill_attachment.py
new file mode 100644
index 0000000..0960d64
--- /dev/null
+++ b/src/opencode/types/shared/prompt_skill_attachment.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from .skill_id import SkillID
+from ..._models import BaseModel
+from .skill_name import SkillName
+from .prompt_mention import PromptMention
+
+__all__ = ["PromptSkillAttachment"]
+
+
+class PromptSkillAttachment(BaseModel):
+ id: SkillID
+
+ name: SkillName
+
+ mention: Optional[PromptMention] = None
+
+ text: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/provider_compaction.py b/src/opencode/types/shared/provider_compaction.py
new file mode 100644
index 0000000..fe49656
--- /dev/null
+++ b/src/opencode/types/shared/provider_compaction.py
@@ -0,0 +1,45 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ProviderCompaction", "ProviderCompactionVariant0", "ProviderCompactionVariant1"]
+
+
+class ProviderCompactionVariant0(BaseModel):
+ type: Literal["summary"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class ProviderCompactionVariant1(BaseModel):
+ type: Literal["native"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+ProviderCompaction: TypeAlias = Union[ProviderCompactionVariant0, ProviderCompactionVariant1]
diff --git a/src/opencode/types/shared/provider_compatibility.py b/src/opencode/types/shared/provider_compatibility.py
new file mode 100644
index 0000000..cda3180
--- /dev/null
+++ b/src/opencode/types/shared/provider_compatibility.py
@@ -0,0 +1,42 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .model_cache_format import ModelCacheFormat
+from .model_reasoning_field import ModelReasoningField
+from .model_max_tokens_field import ModelMaxTokensField
+
+__all__ = ["ProviderCompatibility"]
+
+
+class ProviderCompatibility(BaseModel):
+ cache_format: Optional[ModelCacheFormat] = FieldInfo(alias="cacheFormat", default=None)
+
+ max_tokens_field: Optional[ModelMaxTokensField] = FieldInfo(alias="maxTokensField", default=None)
+
+ reasoning_field: Optional[ModelReasoningField] = FieldInfo(alias="reasoningField", default=None)
+
+ require_assistant_after_tool: Optional[bool] = FieldInfo(alias="requireAssistantAfterTool", default=None)
+
+ require_finish_reason: Optional[bool] = FieldInfo(alias="requireFinishReason", default=None)
+
+ require_reasoning: Optional[bool] = FieldInfo(alias="requireReasoning", default=None)
+
+ stream_usage: Optional[bool] = FieldInfo(alias="streamUsage", default=None)
+
+ supports_prompt_cache_key: Optional[bool] = FieldInfo(alias="supportsPromptCacheKey", default=None)
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/provider_encoding.py b/src/opencode/types/shared/provider_encoding.py
new file mode 100644
index 0000000..05ca17d
--- /dev/null
+++ b/src/opencode/types/shared/provider_encoding.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["ProviderEncoding"]
+
+ProviderEncoding: TypeAlias = Literal["zstd", "gzip", "none"]
diff --git a/src/opencode/types/shared/provider_id.py b/src/opencode/types/shared/provider_id.py
new file mode 100644
index 0000000..f8d3b0c
--- /dev/null
+++ b/src/opencode/types/shared/provider_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["ProviderID"]
+
+ProviderID: TypeAlias = str
diff --git a/src/opencode/types/shared/provider_info.py b/src/opencode/types/shared/provider_info.py
new file mode 100644
index 0000000..a15bfaf
--- /dev/null
+++ b/src/opencode/types/shared/provider_info.py
@@ -0,0 +1,48 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .provider_id import ProviderID
+from .integration_id import IntegrationID
+from .provider_settings import ProviderSettings
+from .provider_compatibility import ProviderCompatibility
+
+__all__ = ["ProviderInfo"]
+
+
+class ProviderInfo(BaseModel):
+ id: ProviderID
+
+ activation: Literal["auto", "enabled", "disabled"]
+
+ name: str
+
+ package: str
+
+ body: Optional[Dict[str, object]] = None
+
+ canonical: Optional[ProviderID] = None
+
+ compatibility: Optional[ProviderCompatibility] = None
+
+ headers: Optional[Dict[str, str]] = None
+
+ integration_id: Optional[IntegrationID] = FieldInfo(alias="integrationID", default=None)
+
+ settings: Optional[ProviderSettings] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/provider_request.py b/src/opencode/types/shared/provider_request.py
new file mode 100644
index 0000000..1792131
--- /dev/null
+++ b/src/opencode/types/shared/provider_request.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .provider_settings import ProviderSettings
+
+__all__ = ["ProviderRequest"]
+
+
+class ProviderRequest(BaseModel):
+ body: Dict[str, object]
+
+ headers: Dict[str, str]
+
+ settings: ProviderSettings
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/provider_settings.py b/src/opencode/types/shared/provider_settings.py
new file mode 100644
index 0000000..9c91831
--- /dev/null
+++ b/src/opencode/types/shared/provider_settings.py
@@ -0,0 +1,39 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .provider_encoding import ProviderEncoding
+from .provider_transport import ProviderTransport
+from .provider_compaction import ProviderCompaction
+
+__all__ = ["ProviderSettings"]
+
+
+class ProviderSettings(BaseModel):
+ chunk_timeout: Union[Literal[False], float, None] = FieldInfo(alias="chunkTimeout", default=None)
+
+ compaction: Optional[ProviderCompaction] = None
+
+ encoding: Optional[ProviderEncoding] = None
+
+ header_timeout: Union[Literal[False], float, None] = FieldInfo(alias="headerTimeout", default=None)
+
+ timeout: Union[Literal[False], float, None] = None
+
+ transport: Optional[ProviderTransport] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/provider_transport.py b/src/opencode/types/shared/provider_transport.py
new file mode 100644
index 0000000..27e537d
--- /dev/null
+++ b/src/opencode/types/shared/provider_transport.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["ProviderTransport"]
+
+ProviderTransport: TypeAlias = Literal["http", "websocket"]
diff --git a/src/opencode/types/shared/provider_updated_event.py b/src/opencode/types/shared/provider_updated_event.py
new file mode 100644
index 0000000..8daad46
--- /dev/null
+++ b/src/opencode/types/shared/provider_updated_event.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["ProviderUpdatedEvent"]
+
+
+class ProviderUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: object
+
+ type: Literal["provider.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/pty.py b/src/opencode/types/shared/pty.py
new file mode 100644
index 0000000..c83f10e
--- /dev/null
+++ b/src/opencode/types/shared/pty.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .pty_id import PtyID
+from ..._models import BaseModel
+
+__all__ = ["Pty"]
+
+
+class Pty(BaseModel):
+ id: PtyID
+
+ args: List[str]
+
+ command: str
+
+ cwd: str
+
+ pid: int
+
+ status: Literal["running", "exited"]
+
+ title: str
+
+ exit_code: Optional[int] = FieldInfo(alias="exitCode", default=None)
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/pty_created_event.py b/src/opencode/types/shared/pty_created_event.py
new file mode 100644
index 0000000..9b95fce
--- /dev/null
+++ b/src/opencode/types/shared/pty_created_event.py
@@ -0,0 +1,55 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .pty import Pty
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["PtyCreatedEvent", "Data"]
+
+
+class Data(BaseModel):
+ info: Pty
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class PtyCreatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["pty.created"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/pty_deleted_event.py b/src/opencode/types/shared/pty_deleted_event.py
new file mode 100644
index 0000000..5a4986b
--- /dev/null
+++ b/src/opencode/types/shared/pty_deleted_event.py
@@ -0,0 +1,55 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .pty_id import PtyID
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["PtyDeletedEvent", "Data"]
+
+
+class Data(BaseModel):
+ id: PtyID
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class PtyDeletedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["pty.deleted"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/pty_exited_event.py b/src/opencode/types/shared/pty_exited_event.py
new file mode 100644
index 0000000..ed9eb47
--- /dev/null
+++ b/src/opencode/types/shared/pty_exited_event.py
@@ -0,0 +1,57 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .pty_id import PtyID
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["PtyExitedEvent", "Data"]
+
+
+class Data(BaseModel):
+ id: PtyID
+
+ exit_code: int = FieldInfo(alias="exitCode")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class PtyExitedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["pty.exited"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/pty_id.py b/src/opencode/types/shared/pty_id.py
new file mode 100644
index 0000000..7a8ea76
--- /dev/null
+++ b/src/opencode/types/shared/pty_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["PtyID"]
+
+PtyID: TypeAlias = str
diff --git a/src/opencode/types/shared/pty_ticket_connect_token.py b/src/opencode/types/shared/pty_ticket_connect_token.py
new file mode 100644
index 0000000..2a5362e
--- /dev/null
+++ b/src/opencode/types/shared/pty_ticket_connect_token.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["PtyTicketConnectToken"]
+
+
+class PtyTicketConnectToken(BaseModel):
+ expires_in: int
+
+ ticket: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/pty_updated_event.py b/src/opencode/types/shared/pty_updated_event.py
new file mode 100644
index 0000000..b55ac25
--- /dev/null
+++ b/src/opencode/types/shared/pty_updated_event.py
@@ -0,0 +1,55 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .pty import Pty
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["PtyUpdatedEvent", "Data"]
+
+
+class Data(BaseModel):
+ info: Pty
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class PtyUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["pty.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/reference_git_source.py b/src/opencode/types/shared/reference_git_source.py
new file mode 100644
index 0000000..b9c58ef
--- /dev/null
+++ b/src/opencode/types/shared/reference_git_source.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ReferenceGitSource"]
+
+
+class ReferenceGitSource(BaseModel):
+ repository: str
+
+ type: Literal["git"]
+
+ branch: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/reference_info.py b/src/opencode/types/shared/reference_info.py
new file mode 100644
index 0000000..3b7fd10
--- /dev/null
+++ b/src/opencode/types/shared/reference_info.py
@@ -0,0 +1,35 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .absolute_path import AbsolutePath
+from .reference_source import ReferenceSource
+
+__all__ = ["ReferenceInfo"]
+
+
+class ReferenceInfo(BaseModel):
+ name: str
+
+ path: AbsolutePath
+
+ source: ReferenceSource
+
+ description: Optional[str] = None
+
+ hidden: Optional[bool] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/reference_local_source.py b/src/opencode/types/shared/reference_local_source.py
new file mode 100644
index 0000000..e9d48a8
--- /dev/null
+++ b/src/opencode/types/shared/reference_local_source.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .absolute_path import AbsolutePath
+
+__all__ = ["ReferenceLocalSource"]
+
+
+class ReferenceLocalSource(BaseModel):
+ path: AbsolutePath
+
+ type: Literal["local"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/reference_source.py b/src/opencode/types/shared/reference_source.py
new file mode 100644
index 0000000..3e505c4
--- /dev/null
+++ b/src/opencode/types/shared/reference_source.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import TypeAlias
+
+from .reference_git_source import ReferenceGitSource
+from .reference_local_source import ReferenceLocalSource
+
+__all__ = ["ReferenceSource"]
+
+ReferenceSource: TypeAlias = Union[ReferenceLocalSource, ReferenceGitSource]
diff --git a/src/opencode/types/shared/reference_updated_event.py b/src/opencode/types/shared/reference_updated_event.py
new file mode 100644
index 0000000..beb3584
--- /dev/null
+++ b/src/opencode/types/shared/reference_updated_event.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["ReferenceUpdatedEvent"]
+
+
+class ReferenceUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: object
+
+ type: Literal["reference.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/relative_path.py b/src/opencode/types/shared/relative_path.py
new file mode 100644
index 0000000..e65ccb5
--- /dev/null
+++ b/src/opencode/types/shared/relative_path.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["RelativePath"]
+
+RelativePath: TypeAlias = str
diff --git a/src/opencode/types/shared/rpc_event.py b/src/opencode/types/shared/rpc_event.py
new file mode 100644
index 0000000..6ec756e
--- /dev/null
+++ b/src/opencode/types/shared/rpc_event.py
@@ -0,0 +1,37 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_public_ref import LocationPublicRef
+
+__all__ = ["RpcEvent"]
+
+
+class RpcEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Dict[str, object]
+
+ location: LocationPublicRef
+
+ type: str
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/rpc_output.py b/src/opencode/types/shared/rpc_output.py
new file mode 100644
index 0000000..7ebe024
--- /dev/null
+++ b/src/opencode/types/shared/rpc_output.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["RpcOutput"]
+
+
+class RpcOutput(BaseModel):
+ output: Optional[object] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/server_connected_event.py b/src/opencode/types/shared/server_connected_event.py
new file mode 100644
index 0000000..7c8a91b
--- /dev/null
+++ b/src/opencode/types/shared/server_connected_event.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_public_ref import LocationPublicRef
+
+__all__ = ["ServerConnectedEvent"]
+
+
+class ServerConnectedEvent(BaseModel):
+ id: EventID
+
+ data: object
+
+ type: Literal["server.connected"]
+
+ location: Optional[LocationPublicRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/server_info.py b/src/opencode/types/shared/server_info.py
new file mode 100644
index 0000000..e5441fd
--- /dev/null
+++ b/src/opencode/types/shared/server_info.py
@@ -0,0 +1,65 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ServerInfo", "Paths", "Capabilities"]
+
+
+class Paths(BaseModel):
+ tmp: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Capabilities(BaseModel):
+ persistent_pty: Optional[bool] = FieldInfo(alias="persistentPty", default=None)
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class ServerInfo(BaseModel):
+ paths: Paths
+
+ pid: int
+
+ urls: List[str]
+
+ version: str
+
+ capabilities: Optional[Capabilities] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_agent_selected_event.py b/src/opencode/types/shared/session_agent_selected_event.py
new file mode 100644
index 0000000..00b5b7f
--- /dev/null
+++ b/src/opencode/types/shared/session_agent_selected_event.py
@@ -0,0 +1,84 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .agent_id import AgentID
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+
+__all__ = ["SessionAgentSelectedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ agent: AgentID
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ previous: Optional[AgentID] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionAgentSelectedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.agent.selected"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_archived_event.py b/src/opencode/types/shared/session_archived_event.py
new file mode 100644
index 0000000..e59c1c1
--- /dev/null
+++ b/src/opencode/types/shared/session_archived_event.py
@@ -0,0 +1,79 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+
+__all__ = ["SessionArchivedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionArchivedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.archived"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_compaction_delta_event.py b/src/opencode/types/shared/session_compaction_delta_event.py
new file mode 100644
index 0000000..4337d1f
--- /dev/null
+++ b/src/opencode/types/shared/session_compaction_delta_event.py
@@ -0,0 +1,57 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .session_id import SessionID
+from .location_ref import LocationRef
+
+__all__ = ["SessionCompactionDeltaEvent", "Data"]
+
+
+class Data(BaseModel):
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ text: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionCompactionDeltaEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["session.compaction.delta"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_compaction_ended_event.py b/src/opencode/types/shared/session_compaction_ended_event.py
new file mode 100644
index 0000000..dd85e20
--- /dev/null
+++ b/src/opencode/types/shared/session_compaction_ended_event.py
@@ -0,0 +1,95 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .model_ref import ModelRef
+from .money_usd import MoneyUSD
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .token_usage_info import TokenUsageInfo
+from .session_message_native4 import SessionMessageNative4
+
+__all__ = ["SessionCompactionEndedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ reason: Literal["auto", "manual"]
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ text: str
+
+ cost: Optional[MoneyUSD] = None
+
+ model: Optional[ModelRef] = None
+
+ native: Optional[SessionMessageNative4] = None
+
+ tokens: Optional[TokenUsageInfo] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionCompactionEndedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.compaction.ended"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_compaction_failed_event.py b/src/opencode/types/shared/session_compaction_failed_event.py
new file mode 100644
index 0000000..a6d8c04
--- /dev/null
+++ b/src/opencode/types/shared/session_compaction_failed_event.py
@@ -0,0 +1,93 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .money_usd import MoneyUSD
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .token_usage_info import TokenUsageInfo
+from .session_message_id import SessionMessageID
+from .session_structured_error import SessionStructuredError
+
+__all__ = ["SessionCompactionFailedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ error: SessionStructuredError
+
+ reason: Literal["auto", "manual"]
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ cost: Optional[MoneyUSD] = None
+
+ input_id: Optional[SessionMessageID] = FieldInfo(alias="inputID", default=None)
+
+ tokens: Optional[TokenUsageInfo] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionCompactionFailedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.compaction.failed"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_compaction_started_event.py b/src/opencode/types/shared/session_compaction_started_event.py
new file mode 100644
index 0000000..3fa36b7
--- /dev/null
+++ b/src/opencode/types/shared/session_compaction_started_event.py
@@ -0,0 +1,84 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionCompactionStartedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ reason: Literal["auto", "manual"]
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ input_id: Optional[SessionMessageID] = FieldInfo(alias="inputID", default=None)
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionCompactionStartedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.compaction.started"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_created_event.py b/src/opencode/types/shared/session_created_event.py
new file mode 100644
index 0000000..2c25d8c
--- /dev/null
+++ b/src/opencode/types/shared/session_created_event.py
@@ -0,0 +1,107 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .agent_id import AgentID
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .model_ref import ModelRef
+from .project_id import ProjectID
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .relative_path import RelativePath
+from .session_metadata import SessionMetadata
+from .permission_ruleset import PermissionRuleset
+
+__all__ = ["SessionCreatedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ location: LocationRef
+
+ project_id: ProjectID = FieldInfo(alias="projectID")
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ slug: str
+
+ version: str
+
+ agent: Optional[AgentID] = None
+
+ metadata: Optional[SessionMetadata] = None
+
+ model: Optional[ModelRef] = None
+
+ parent_id: Optional[SessionID] = FieldInfo(alias="parentID", default=None)
+
+ permissions: Optional[PermissionRuleset] = None
+
+ subpath: Optional[RelativePath] = None
+
+ title: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionCreatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.created"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_deleted_event.py b/src/opencode/types/shared/session_deleted_event.py
new file mode 100644
index 0000000..d3becf8
--- /dev/null
+++ b/src/opencode/types/shared/session_deleted_event.py
@@ -0,0 +1,79 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version10 import EventVersion10
+
+__all__ = ["SessionDeletedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion10
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionDeletedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.deleted"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_execution_failed_event.py b/src/opencode/types/shared/session_execution_failed_event.py
new file mode 100644
index 0000000..fa63906
--- /dev/null
+++ b/src/opencode/types/shared/session_execution_failed_event.py
@@ -0,0 +1,82 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_structured_error import SessionStructuredError
+
+__all__ = ["SessionExecutionFailedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ error: SessionStructuredError
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionExecutionFailedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.execution.failed"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_execution_interrupted_event.py b/src/opencode/types/shared/session_execution_interrupted_event.py
new file mode 100644
index 0000000..8a74f27
--- /dev/null
+++ b/src/opencode/types/shared/session_execution_interrupted_event.py
@@ -0,0 +1,81 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+
+__all__ = ["SessionExecutionInterruptedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ reason: Literal["user", "shutdown", "superseded", "inactivity"]
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionExecutionInterruptedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.execution.interrupted"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_execution_started_event.py b/src/opencode/types/shared/session_execution_started_event.py
new file mode 100644
index 0000000..7ead9cf
--- /dev/null
+++ b/src/opencode/types/shared/session_execution_started_event.py
@@ -0,0 +1,79 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+
+__all__ = ["SessionExecutionStartedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionExecutionStartedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.execution.started"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_execution_succeeded_event.py b/src/opencode/types/shared/session_execution_succeeded_event.py
new file mode 100644
index 0000000..7860803
--- /dev/null
+++ b/src/opencode/types/shared/session_execution_succeeded_event.py
@@ -0,0 +1,79 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+
+__all__ = ["SessionExecutionSucceededEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionExecutionSucceededEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.execution.succeeded"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_fork_boundary.py b/src/opencode/types/shared/session_fork_boundary.py
new file mode 100644
index 0000000..1cbde10
--- /dev/null
+++ b/src/opencode/types/shared/session_fork_boundary.py
@@ -0,0 +1,50 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionForkBoundary", "SessionForkBoundaryVariant0", "SessionForkBoundaryVariant1"]
+
+
+class SessionForkBoundaryVariant0(BaseModel):
+ message_id: SessionMessageID = FieldInfo(alias="messageID")
+
+ type: Literal["before"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionForkBoundaryVariant1(BaseModel):
+ message_id: SessionMessageID = FieldInfo(alias="messageID")
+
+ type: Literal["through"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+SessionForkBoundary: TypeAlias = Union[SessionForkBoundaryVariant0, SessionForkBoundaryVariant1]
diff --git a/src/opencode/types/shared/session_forked_event.py b/src/opencode/types/shared/session_forked_event.py
new file mode 100644
index 0000000..1a4f62d
--- /dev/null
+++ b/src/opencode/types/shared/session_forked_event.py
@@ -0,0 +1,89 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version10 import EventVersion10
+from .session_fork_boundary import SessionForkBoundary
+from .instruction_entry_snapshot import InstructionEntrySnapshot
+
+__all__ = ["SessionForkedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ boundary: SessionForkBoundary
+
+ parent_id: SessionID = FieldInfo(alias="parentID")
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ instruction_entries: Optional[InstructionEntrySnapshot] = FieldInfo(alias="instructionEntries", default=None)
+
+ instructions: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion10
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionForkedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.forked"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_generate_response.py b/src/opencode/types/shared/session_generate_response.py
new file mode 100644
index 0000000..7fe7196
--- /dev/null
+++ b/src/opencode/types/shared/session_generate_response.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["SessionGenerateResponse", "Data"]
+
+
+class Data(BaseModel):
+ text: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionGenerateResponse(BaseModel):
+ data: Data
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_id.py b/src/opencode/types/shared/session_id.py
new file mode 100644
index 0000000..e25c777
--- /dev/null
+++ b/src/opencode/types/shared/session_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionID"]
+
+SessionID: TypeAlias = str
diff --git a/src/opencode/types/shared/session_id1.py b/src/opencode/types/shared/session_id1.py
new file mode 100644
index 0000000..65b9684
--- /dev/null
+++ b/src/opencode/types/shared/session_id1.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionID1"]
+
+SessionID1: TypeAlias = str
diff --git a/src/opencode/types/shared/session_idle_event.py b/src/opencode/types/shared/session_idle_event.py
new file mode 100644
index 0000000..abba8ff
--- /dev/null
+++ b/src/opencode/types/shared/session_idle_event.py
@@ -0,0 +1,55 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .session_id import SessionID
+from .location_ref import LocationRef
+
+__all__ = ["SessionIdleEvent", "Data"]
+
+
+class Data(BaseModel):
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionIdleEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["session.idle"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_inbox_cancelled_event.py b/src/opencode/types/shared/session_inbox_cancelled_event.py
new file mode 100644
index 0000000..4302f6b
--- /dev/null
+++ b/src/opencode/types/shared/session_inbox_cancelled_event.py
@@ -0,0 +1,82 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionInboxCancelledEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ inbox_id: SessionMessageID = FieldInfo(alias="inboxID")
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionInboxCancelledEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.inbox.cancelled"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_inbox_compaction.py b/src/opencode/types/shared/session_inbox_compaction.py
new file mode 100644
index 0000000..82eda8f
--- /dev/null
+++ b/src/opencode/types/shared/session_inbox_compaction.py
@@ -0,0 +1,56 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_id import SessionID
+from .session_message_id import SessionMessageID
+from .session_inbox_delivery import SessionInboxDelivery
+from .session_inbox_compaction_payload import SessionInboxCompactionPayload
+
+__all__ = ["SessionInboxCompaction", "Time"]
+
+
+class Time(BaseModel):
+ created: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionInboxCompaction(BaseModel):
+ id: SessionMessageID
+
+ delivery: SessionInboxDelivery
+
+ payload: SessionInboxCompactionPayload
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ time: Time
+
+ type: Literal["compaction"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_inbox_compaction_payload.py b/src/opencode/types/shared/session_inbox_compaction_payload.py
new file mode 100644
index 0000000..13361b7
--- /dev/null
+++ b/src/opencode/types/shared/session_inbox_compaction_payload.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from ..._models import BaseModel
+
+__all__ = ["SessionInboxCompactionPayload"]
+
+
+class SessionInboxCompactionPayload(BaseModel):
+ pass
diff --git a/src/opencode/types/shared/session_inbox_delivered_event.py b/src/opencode/types/shared/session_inbox_delivered_event.py
new file mode 100644
index 0000000..b679f63
--- /dev/null
+++ b/src/opencode/types/shared/session_inbox_delivered_event.py
@@ -0,0 +1,82 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionInboxDeliveredEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ inbox_id: SessionMessageID = FieldInfo(alias="inboxID")
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionInboxDeliveredEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.inbox.delivered"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_inbox_delivery.py b/src/opencode/types/shared/session_inbox_delivery.py
new file mode 100644
index 0000000..e4ee047
--- /dev/null
+++ b/src/opencode/types/shared/session_inbox_delivery.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["SessionInboxDelivery"]
+
+SessionInboxDelivery: TypeAlias = Literal["steer", "queue"]
diff --git a/src/opencode/types/shared/session_inbox_delivery_changed_event.py b/src/opencode/types/shared/session_inbox_delivery_changed_event.py
new file mode 100644
index 0000000..72ba092
--- /dev/null
+++ b/src/opencode/types/shared/session_inbox_delivery_changed_event.py
@@ -0,0 +1,85 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_message_id import SessionMessageID
+from .session_inbox_delivery import SessionInboxDelivery
+
+__all__ = ["SessionInboxDeliveryChangedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ delivery: SessionInboxDelivery
+
+ inbox_id: SessionMessageID = FieldInfo(alias="inboxID")
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionInboxDeliveryChangedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.inbox.delivery.changed"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_inbox_enqueued_event.py b/src/opencode/types/shared/session_inbox_enqueued_event.py
new file mode 100644
index 0000000..3c9f9d4
--- /dev/null
+++ b/src/opencode/types/shared/session_inbox_enqueued_event.py
@@ -0,0 +1,85 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_inbox_item import SessionInboxItem
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionInboxEnqueuedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ inbox_id: SessionMessageID = FieldInfo(alias="inboxID")
+
+ item: SessionInboxItem
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionInboxEnqueuedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.inbox.enqueued"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_inbox_info.py b/src/opencode/types/shared/session_inbox_info.py
new file mode 100644
index 0000000..73cee98
--- /dev/null
+++ b/src/opencode/types/shared/session_inbox_info.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import TypeAlias
+
+from .session_inbox_move import SessionInboxMove
+from .session_inbox_user import SessionInboxUser
+from .session_inbox_synthetic import SessionInboxSynthetic
+from .session_inbox_compaction import SessionInboxCompaction
+
+__all__ = ["SessionInboxInfo"]
+
+SessionInboxInfo: TypeAlias = Union[SessionInboxUser, SessionInboxSynthetic, SessionInboxCompaction, SessionInboxMove]
diff --git a/src/opencode/types/shared/session_inbox_item.py b/src/opencode/types/shared/session_inbox_item.py
new file mode 100644
index 0000000..da3fdd6
--- /dev/null
+++ b/src/opencode/types/shared/session_inbox_item.py
@@ -0,0 +1,106 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_inbox_delivery import SessionInboxDelivery
+from .session_inbox_user_payload import SessionInboxUserPayload
+from .session_inbox_move_payload1 import SessionInboxMovePayload1
+from .session_inbox_synthetic_payload import SessionInboxSyntheticPayload
+from .session_inbox_compaction_payload import SessionInboxCompactionPayload
+
+__all__ = [
+ "SessionInboxItem",
+ "SessionInboxItemVariant0",
+ "SessionInboxItemVariant1",
+ "SessionInboxItemVariant2",
+ "SessionInboxItemVariant3",
+]
+
+
+class SessionInboxItemVariant0(BaseModel):
+ delivery: SessionInboxDelivery
+
+ payload: SessionInboxUserPayload
+
+ type: Literal["user"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionInboxItemVariant1(BaseModel):
+ delivery: SessionInboxDelivery
+
+ payload: SessionInboxSyntheticPayload
+
+ type: Literal["synthetic"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionInboxItemVariant2(BaseModel):
+ delivery: SessionInboxDelivery
+
+ payload: SessionInboxCompactionPayload
+
+ type: Literal["compaction"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionInboxItemVariant3(BaseModel):
+ delivery: SessionInboxDelivery
+
+ payload: SessionInboxMovePayload1
+
+ type: Literal["move"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+SessionInboxItem: TypeAlias = Union[
+ SessionInboxItemVariant0, SessionInboxItemVariant1, SessionInboxItemVariant2, SessionInboxItemVariant3
+]
diff --git a/src/opencode/types/shared/session_inbox_move.py b/src/opencode/types/shared/session_inbox_move.py
new file mode 100644
index 0000000..e6b6680
--- /dev/null
+++ b/src/opencode/types/shared/session_inbox_move.py
@@ -0,0 +1,56 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_id import SessionID
+from .session_message_id import SessionMessageID
+from .session_inbox_delivery import SessionInboxDelivery
+from .session_inbox_move_payload import SessionInboxMovePayload
+
+__all__ = ["SessionInboxMove", "Time"]
+
+
+class Time(BaseModel):
+ created: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionInboxMove(BaseModel):
+ id: SessionMessageID
+
+ delivery: SessionInboxDelivery
+
+ payload: SessionInboxMovePayload
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ time: Time
+
+ type: Literal["move"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_inbox_move_payload.py b/src/opencode/types/shared/session_inbox_move_payload.py
new file mode 100644
index 0000000..8db2095
--- /dev/null
+++ b/src/opencode/types/shared/session_inbox_move_payload.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .project_id import ProjectID
+from .relative_path import RelativePath
+from .location_public_ref import LocationPublicRef
+
+__all__ = ["SessionInboxMovePayload"]
+
+
+class SessionInboxMovePayload(BaseModel):
+ location: LocationPublicRef
+
+ project_id: ProjectID = FieldInfo(alias="projectID")
+
+ subpath: Optional[RelativePath] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_inbox_move_payload1.py b/src/opencode/types/shared/session_inbox_move_payload1.py
new file mode 100644
index 0000000..3366734
--- /dev/null
+++ b/src/opencode/types/shared/session_inbox_move_payload1.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .project_id import ProjectID
+from .location_ref import LocationRef
+from .relative_path import RelativePath
+
+__all__ = ["SessionInboxMovePayload1"]
+
+
+class SessionInboxMovePayload1(BaseModel):
+ location: LocationRef
+
+ project_id: ProjectID = FieldInfo(alias="projectID")
+
+ subpath: Optional[RelativePath] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_inbox_synthetic.py b/src/opencode/types/shared/session_inbox_synthetic.py
new file mode 100644
index 0000000..167f903
--- /dev/null
+++ b/src/opencode/types/shared/session_inbox_synthetic.py
@@ -0,0 +1,56 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_id import SessionID
+from .session_message_id import SessionMessageID
+from .session_inbox_delivery import SessionInboxDelivery
+from .session_inbox_synthetic_payload import SessionInboxSyntheticPayload
+
+__all__ = ["SessionInboxSynthetic", "Time"]
+
+
+class Time(BaseModel):
+ created: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionInboxSynthetic(BaseModel):
+ id: SessionMessageID
+
+ delivery: SessionInboxDelivery
+
+ payload: SessionInboxSyntheticPayload
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ time: Time
+
+ type: Literal["synthetic"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_inbox_synthetic_payload.py b/src/opencode/types/shared/session_inbox_synthetic_payload.py
new file mode 100644
index 0000000..654e1d5
--- /dev/null
+++ b/src/opencode/types/shared/session_inbox_synthetic_payload.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["SessionInboxSyntheticPayload"]
+
+
+class SessionInboxSyntheticPayload(BaseModel):
+ text: str
+
+ description: Optional[str] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_inbox_user.py b/src/opencode/types/shared/session_inbox_user.py
new file mode 100644
index 0000000..5d29d8f
--- /dev/null
+++ b/src/opencode/types/shared/session_inbox_user.py
@@ -0,0 +1,56 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_id import SessionID
+from .session_message_id import SessionMessageID
+from .session_inbox_delivery import SessionInboxDelivery
+from .session_inbox_user_payload import SessionInboxUserPayload
+
+__all__ = ["SessionInboxUser", "Time"]
+
+
+class Time(BaseModel):
+ created: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionInboxUser(BaseModel):
+ id: SessionMessageID
+
+ delivery: SessionInboxDelivery
+
+ payload: SessionInboxUserPayload
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ time: Time
+
+ type: Literal["user"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_inbox_user_payload.py b/src/opencode/types/shared/session_inbox_user_payload.py
new file mode 100644
index 0000000..bf2e370
--- /dev/null
+++ b/src/opencode/types/shared/session_inbox_user_payload.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .prompt_file_attachment import PromptFileAttachment
+from .prompt_agent_attachment import PromptAgentAttachment
+from .prompt_skill_attachment import PromptSkillAttachment
+
+__all__ = ["SessionInboxUserPayload"]
+
+
+class SessionInboxUserPayload(BaseModel):
+ text: str
+
+ agents: Optional[List[PromptAgentAttachment]] = None
+
+ files: Optional[List[PromptFileAttachment]] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ skills: Optional[List[PromptSkillAttachment]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_info.py b/src/opencode/types/shared/session_info.py
new file mode 100644
index 0000000..87ae2da
--- /dev/null
+++ b/src/opencode/types/shared/session_info.py
@@ -0,0 +1,110 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .agent_id import AgentID
+from ..._models import BaseModel
+from .model_ref import ModelRef
+from .money_usd import MoneyUSD
+from .project_id import ProjectID
+from .session_id import SessionID
+from .relative_path import RelativePath
+from .session_revert import SessionRevert
+from .session_metadata import SessionMetadata
+from .token_usage_info import TokenUsageInfo
+from .permission_ruleset import PermissionRuleset
+from .location_public_ref import LocationPublicRef
+from .session_fork_boundary import SessionForkBoundary
+
+__all__ = ["SessionInfo", "Time", "Fork"]
+
+
+class Time(BaseModel):
+ created: float
+
+ updated: float
+
+ archived: Optional[float] = None
+
+ idle: Optional[float] = None
+
+ viewed: Optional[float] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Fork(BaseModel):
+ boundary: SessionForkBoundary
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionInfo(BaseModel):
+ id: SessionID
+
+ cost: MoneyUSD
+
+ location: LocationPublicRef
+
+ project_id: ProjectID = FieldInfo(alias="projectID")
+
+ time: Time
+
+ tokens: TokenUsageInfo
+
+ agent: Optional[AgentID] = None
+
+ fork: Optional[Fork] = None
+
+ metadata: Optional[SessionMetadata] = None
+
+ model: Optional[ModelRef] = None
+
+ outcome: Optional[Literal["succeeded", "failed", "interrupted"]] = None
+
+ parent_id: Optional[SessionID] = FieldInfo(alias="parentID", default=None)
+
+ permissions: Optional[PermissionRuleset] = None
+
+ revert: Optional[SessionRevert] = None
+
+ subpath: Optional[RelativePath] = None
+
+ title: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_instructions_updated_event.py b/src/opencode/types/shared/session_instructions_updated_event.py
new file mode 100644
index 0000000..1ccfcd9
--- /dev/null
+++ b/src/opencode/types/shared/session_instructions_updated_event.py
@@ -0,0 +1,86 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union, Optional
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version10 import EventVersion10
+from .instruction_hash import InstructionHash
+
+__all__ = ["SessionInstructionsUpdatedEvent", "Data", "DataDelta", "Durable"]
+
+DataDelta: TypeAlias = Union[Literal["removed"], InstructionHash]
+
+
+class Data(BaseModel):
+ delta: Dict[str, DataDelta]
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ text: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion10
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionInstructionsUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.instructions.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_interrupt_response.py b/src/opencode/types/shared/session_interrupt_response.py
new file mode 100644
index 0000000..2c22f18
--- /dev/null
+++ b/src/opencode/types/shared/session_interrupt_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["SessionInterruptResponse"]
+
+
+class SessionInterruptResponse(BaseModel):
+ interrupted: bool
+ """Whether an active execution owned by this OpenCode process was interrupted."""
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_agent_selected.py b/src/opencode/types/shared/session_message_agent_selected.py
new file mode 100644
index 0000000..458b617
--- /dev/null
+++ b/src/opencode/types/shared/session_message_agent_selected.py
@@ -0,0 +1,54 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .agent_id import AgentID
+from ..._models import BaseModel
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionMessageAgentSelected", "Time"]
+
+
+class Time(BaseModel):
+ created: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionMessageAgentSelected(BaseModel):
+ id: SessionMessageID
+
+ agent: AgentID
+
+ time: Time
+
+ type: Literal["agent-switched"]
+
+ metadata: Optional[Dict[str, object]] = None
+
+ previous: Optional[AgentID] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_assistant.py b/src/opencode/types/shared/session_message_assistant.py
new file mode 100644
index 0000000..c468020
--- /dev/null
+++ b/src/opencode/types/shared/session_message_assistant.py
@@ -0,0 +1,109 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Union, Optional
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from .agent_id import AgentID
+from ..._models import BaseModel
+from .model_ref import ModelRef
+from .money_usd import MoneyUSD
+from .snapshot_id import SnapshotID
+from .relative_path import RelativePath
+from .token_usage_info import TokenUsageInfo
+from .session_message_id import SessionMessageID
+from .session_message_native3 import SessionMessageNative3
+from .session_structured_error import SessionStructuredError
+from .session_message_assistant_text import SessionMessageAssistantText
+from .session_message_assistant_tool import SessionMessageAssistantTool
+from .session_message_assistant_retry import SessionMessageAssistantRetry
+from .session_message_assistant_reasoning import SessionMessageAssistantReasoning
+
+__all__ = ["SessionMessageAssistant", "Content", "Time", "Snapshot"]
+
+Content: TypeAlias = Union[SessionMessageAssistantText, SessionMessageAssistantReasoning, SessionMessageAssistantTool]
+
+
+class Time(BaseModel):
+ created: float
+
+ completed: Optional[float] = None
+
+ streamed: Optional[float] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Snapshot(BaseModel):
+ end: Optional[SnapshotID] = None
+
+ files: Optional[List[RelativePath]] = None
+
+ start: Optional[SnapshotID] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionMessageAssistant(BaseModel):
+ id: SessionMessageID
+
+ agent: AgentID
+
+ content: List[Content]
+
+ model: ModelRef
+
+ time: Time
+
+ type: Literal["assistant"]
+
+ cost: Optional[MoneyUSD] = None
+
+ error: Optional[SessionStructuredError] = None
+
+ finish: Optional[Literal["stop", "length", "tool-calls", "content-filter", "error", "unknown"]] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ native: Optional[SessionMessageNative3] = None
+
+ raw_finish: Optional[str] = FieldInfo(alias="rawFinish", default=None)
+
+ retry: Optional[SessionMessageAssistantRetry] = None
+
+ snapshot: Optional[Snapshot] = None
+
+ tokens: Optional[TokenUsageInfo] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_assistant_reasoning.py b/src/opencode/types/shared/session_message_assistant_reasoning.py
new file mode 100644
index 0000000..ec64d70
--- /dev/null
+++ b/src/opencode/types/shared/session_message_assistant_reasoning.py
@@ -0,0 +1,51 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_message_native1 import SessionMessageNative1
+
+__all__ = ["SessionMessageAssistantReasoning", "Time"]
+
+
+class Time(BaseModel):
+ created: float
+
+ completed: Optional[float] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionMessageAssistantReasoning(BaseModel):
+ text: str
+
+ type: Literal["reasoning"]
+
+ native: Optional[SessionMessageNative1] = None
+
+ time: Optional[Time] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_assistant_retry.py b/src/opencode/types/shared/session_message_assistant_retry.py
new file mode 100644
index 0000000..8c8b852
--- /dev/null
+++ b/src/opencode/types/shared/session_message_assistant_retry.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_structured_error import SessionStructuredError
+
+__all__ = ["SessionMessageAssistantRetry"]
+
+
+class SessionMessageAssistantRetry(BaseModel):
+ at: float
+
+ attempt: int
+
+ error: SessionStructuredError
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_assistant_text.py b/src/opencode/types/shared/session_message_assistant_text.py
new file mode 100644
index 0000000..2cc4672
--- /dev/null
+++ b/src/opencode/types/shared/session_message_assistant_text.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_message_native import SessionMessageNative
+
+__all__ = ["SessionMessageAssistantText"]
+
+
+class SessionMessageAssistantText(BaseModel):
+ text: str
+
+ type: Literal["text"]
+
+ native: Optional[SessionMessageNative] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_assistant_tool.py b/src/opencode/types/shared/session_message_assistant_tool.py
new file mode 100644
index 0000000..5e02949
--- /dev/null
+++ b/src/opencode/types/shared/session_message_assistant_tool.py
@@ -0,0 +1,70 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union, Optional
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_message_native2 import SessionMessageNative2
+from .session_message_tool_state_error import SessionMessageToolStateError
+from .session_message_tool_state_running import SessionMessageToolStateRunning
+from .session_message_tool_state_completed import SessionMessageToolStateCompleted
+from .session_message_tool_state_streaming import SessionMessageToolStateStreaming
+
+__all__ = ["SessionMessageAssistantTool", "State", "Time"]
+
+State: TypeAlias = Union[
+ SessionMessageToolStateStreaming,
+ SessionMessageToolStateRunning,
+ SessionMessageToolStateCompleted,
+ SessionMessageToolStateError,
+]
+
+
+class Time(BaseModel):
+ created: float
+
+ completed: Optional[float] = None
+
+ ran: Optional[float] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionMessageAssistantTool(BaseModel):
+ id: str
+
+ name: str
+
+ state: State
+
+ time: Time
+
+ type: Literal["tool"]
+
+ executed: Optional[bool] = None
+
+ native: Optional[SessionMessageNative2] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_compaction.py b/src/opencode/types/shared/session_message_compaction.py
new file mode 100644
index 0000000..da4625b
--- /dev/null
+++ b/src/opencode/types/shared/session_message_compaction.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import TypeAlias
+
+from .session_message_compaction_failed import SessionMessageCompactionFailed
+from .session_message_compaction_running import SessionMessageCompactionRunning
+from .session_message_compaction_completed import SessionMessageCompactionCompleted
+
+__all__ = ["SessionMessageCompaction"]
+
+SessionMessageCompaction: TypeAlias = Union[
+ SessionMessageCompactionRunning, SessionMessageCompactionCompleted, SessionMessageCompactionFailed
+]
diff --git a/src/opencode/types/shared/session_message_compaction_completed.py b/src/opencode/types/shared/session_message_compaction_completed.py
new file mode 100644
index 0000000..9dbe7d4
--- /dev/null
+++ b/src/opencode/types/shared/session_message_compaction_completed.py
@@ -0,0 +1,67 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .model_ref import ModelRef
+from .money_usd import MoneyUSD
+from .token_usage_info import TokenUsageInfo
+from .session_message_id import SessionMessageID
+from .session_message_native4 import SessionMessageNative4
+
+__all__ = ["SessionMessageCompactionCompleted", "Time"]
+
+
+class Time(BaseModel):
+ created: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionMessageCompactionCompleted(BaseModel):
+ id: SessionMessageID
+
+ reason: Literal["auto", "manual"]
+
+ status: Literal["completed"]
+
+ summary: str
+
+ time: Time
+
+ type: Literal["compaction"]
+
+ cost: Optional[MoneyUSD] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ model: Optional[ModelRef] = None
+
+ native: Optional[SessionMessageNative4] = None
+
+ tokens: Optional[TokenUsageInfo] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_compaction_failed.py b/src/opencode/types/shared/session_message_compaction_failed.py
new file mode 100644
index 0000000..5138d7a
--- /dev/null
+++ b/src/opencode/types/shared/session_message_compaction_failed.py
@@ -0,0 +1,62 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .money_usd import MoneyUSD
+from .token_usage_info import TokenUsageInfo
+from .session_message_id import SessionMessageID
+from .session_structured_error import SessionStructuredError
+
+__all__ = ["SessionMessageCompactionFailed", "Time"]
+
+
+class Time(BaseModel):
+ created: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionMessageCompactionFailed(BaseModel):
+ id: SessionMessageID
+
+ error: SessionStructuredError
+
+ reason: Literal["auto", "manual"]
+
+ status: Literal["failed"]
+
+ time: Time
+
+ type: Literal["compaction"]
+
+ cost: Optional[MoneyUSD] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ tokens: Optional[TokenUsageInfo] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_compaction_running.py b/src/opencode/types/shared/session_message_compaction_running.py
new file mode 100644
index 0000000..49cbcf2
--- /dev/null
+++ b/src/opencode/types/shared/session_message_compaction_running.py
@@ -0,0 +1,55 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionMessageCompactionRunning", "Time"]
+
+
+class Time(BaseModel):
+ created: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionMessageCompactionRunning(BaseModel):
+ id: SessionMessageID
+
+ reason: Literal["auto", "manual"]
+
+ status: Literal["running"]
+
+ summary: str
+
+ time: Time
+
+ type: Literal["compaction"]
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_id.py b/src/opencode/types/shared/session_message_id.py
new file mode 100644
index 0000000..ffd523d
--- /dev/null
+++ b/src/opencode/types/shared/session_message_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionMessageID"]
+
+SessionMessageID: TypeAlias = str
diff --git a/src/opencode/types/shared/session_message_idle.py b/src/opencode/types/shared/session_message_idle.py
new file mode 100644
index 0000000..ce28b89
--- /dev/null
+++ b/src/opencode/types/shared/session_message_idle.py
@@ -0,0 +1,54 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_message_id import SessionMessageID
+from .session_structured_error import SessionStructuredError
+
+__all__ = ["SessionMessageIdle", "Time"]
+
+
+class Time(BaseModel):
+ created: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionMessageIdle(BaseModel):
+ id: SessionMessageID
+
+ outcome: Literal["succeeded", "failed", "interrupted"]
+
+ time: Time
+
+ type: Literal["idle"]
+
+ error: Optional[SessionStructuredError] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_info.py b/src/opencode/types/shared/session_message_info.py
new file mode 100644
index 0000000..3b43492
--- /dev/null
+++ b/src/opencode/types/shared/session_message_info.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import TypeAlias
+
+from .session_message_idle import SessionMessageIdle
+from .session_message_user import SessionMessageUser
+from .session_message_shell import SessionMessageShell
+from .session_message_skill import SessionMessageSkill
+from .session_message_system import SessionMessageSystem
+from .session_message_assistant import SessionMessageAssistant
+from .session_message_synthetic import SessionMessageSynthetic
+from .session_message_compaction import SessionMessageCompaction
+from .session_message_agent_selected import SessionMessageAgentSelected
+from .session_message_model_selected import SessionMessageModelSelected
+from .session_message_location_switched import SessionMessageLocationSwitched
+
+__all__ = ["SessionMessageInfo"]
+
+SessionMessageInfo: TypeAlias = Union[
+ SessionMessageAgentSelected,
+ SessionMessageModelSelected,
+ SessionMessageLocationSwitched,
+ SessionMessageUser,
+ SessionMessageSynthetic,
+ SessionMessageSystem,
+ SessionMessageSkill,
+ SessionMessageShell,
+ SessionMessageAssistant,
+ SessionMessageCompaction,
+ SessionMessageIdle,
+]
diff --git a/src/opencode/types/shared/session_message_location_switched.py b/src/opencode/types/shared/session_message_location_switched.py
new file mode 100644
index 0000000..4202748
--- /dev/null
+++ b/src/opencode/types/shared/session_message_location_switched.py
@@ -0,0 +1,80 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .project_id import ProjectID
+from .relative_path import RelativePath
+from .session_message_id import SessionMessageID
+from .location_public_ref import LocationPublicRef
+
+__all__ = ["SessionMessageLocationSwitched", "Time", "Previous"]
+
+
+class Time(BaseModel):
+ created: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Previous(BaseModel):
+ location: LocationPublicRef
+
+ project_id: Optional[ProjectID] = FieldInfo(alias="projectID", default=None)
+
+ subpath: Optional[RelativePath] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionMessageLocationSwitched(BaseModel):
+ id: SessionMessageID
+
+ location: LocationPublicRef
+
+ time: Time
+
+ type: Literal["location-switched"]
+
+ metadata: Optional[Dict[str, object]] = None
+
+ previous: Optional[Previous] = None
+
+ project_id: Optional[ProjectID] = FieldInfo(alias="projectID", default=None)
+
+ subpath: Optional[RelativePath] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_model_selected.py b/src/opencode/types/shared/session_message_model_selected.py
new file mode 100644
index 0000000..38d3ebc
--- /dev/null
+++ b/src/opencode/types/shared/session_message_model_selected.py
@@ -0,0 +1,54 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .model_ref import ModelRef
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionMessageModelSelected", "Time"]
+
+
+class Time(BaseModel):
+ created: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionMessageModelSelected(BaseModel):
+ id: SessionMessageID
+
+ model: ModelRef
+
+ time: Time
+
+ type: Literal["model-switched"]
+
+ metadata: Optional[Dict[str, object]] = None
+
+ previous: Optional[ModelRef] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_native.py b/src/opencode/types/shared/session_message_native.py
new file mode 100644
index 0000000..a1b4267
--- /dev/null
+++ b/src/opencode/types/shared/session_message_native.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionMessageNative"]
+
+SessionMessageNative: TypeAlias = Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_native1.py b/src/opencode/types/shared/session_message_native1.py
new file mode 100644
index 0000000..31c5b3a
--- /dev/null
+++ b/src/opencode/types/shared/session_message_native1.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionMessageNative1"]
+
+SessionMessageNative1: TypeAlias = Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_native10.py b/src/opencode/types/shared/session_message_native10.py
new file mode 100644
index 0000000..6f8ca45
--- /dev/null
+++ b/src/opencode/types/shared/session_message_native10.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionMessageNative10"]
+
+SessionMessageNative10: TypeAlias = Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_native11.py b/src/opencode/types/shared/session_message_native11.py
new file mode 100644
index 0000000..6716f3e
--- /dev/null
+++ b/src/opencode/types/shared/session_message_native11.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionMessageNative11"]
+
+SessionMessageNative11: TypeAlias = Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_native12.py b/src/opencode/types/shared/session_message_native12.py
new file mode 100644
index 0000000..b62ebfa
--- /dev/null
+++ b/src/opencode/types/shared/session_message_native12.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionMessageNative12"]
+
+SessionMessageNative12: TypeAlias = Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_native2.py b/src/opencode/types/shared/session_message_native2.py
new file mode 100644
index 0000000..7b8ee5f
--- /dev/null
+++ b/src/opencode/types/shared/session_message_native2.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionMessageNative2"]
+
+SessionMessageNative2: TypeAlias = Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_native3.py b/src/opencode/types/shared/session_message_native3.py
new file mode 100644
index 0000000..04ec241
--- /dev/null
+++ b/src/opencode/types/shared/session_message_native3.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionMessageNative3"]
+
+SessionMessageNative3: TypeAlias = Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_native4.py b/src/opencode/types/shared/session_message_native4.py
new file mode 100644
index 0000000..b61acc0
--- /dev/null
+++ b/src/opencode/types/shared/session_message_native4.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionMessageNative4"]
+
+SessionMessageNative4: TypeAlias = Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_native5.py b/src/opencode/types/shared/session_message_native5.py
new file mode 100644
index 0000000..4d9ac53
--- /dev/null
+++ b/src/opencode/types/shared/session_message_native5.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionMessageNative5"]
+
+SessionMessageNative5: TypeAlias = Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_native6.py b/src/opencode/types/shared/session_message_native6.py
new file mode 100644
index 0000000..94d9bbd
--- /dev/null
+++ b/src/opencode/types/shared/session_message_native6.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionMessageNative6"]
+
+SessionMessageNative6: TypeAlias = Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_native7.py b/src/opencode/types/shared/session_message_native7.py
new file mode 100644
index 0000000..2c074b6
--- /dev/null
+++ b/src/opencode/types/shared/session_message_native7.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionMessageNative7"]
+
+SessionMessageNative7: TypeAlias = Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_native8.py b/src/opencode/types/shared/session_message_native8.py
new file mode 100644
index 0000000..2d0d03c
--- /dev/null
+++ b/src/opencode/types/shared/session_message_native8.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionMessageNative8"]
+
+SessionMessageNative8: TypeAlias = Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_native9.py b/src/opencode/types/shared/session_message_native9.py
new file mode 100644
index 0000000..b46ec2a
--- /dev/null
+++ b/src/opencode/types/shared/session_message_native9.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionMessageNative9"]
+
+SessionMessageNative9: TypeAlias = Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_shell.py b/src/opencode/types/shared/session_message_shell.py
new file mode 100644
index 0000000..23e2c14
--- /dev/null
+++ b/src/opencode/types/shared/session_message_shell.py
@@ -0,0 +1,84 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .shell_id import ShellID
+from ..._models import BaseModel
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionMessageShell", "Time", "Output"]
+
+
+class Time(BaseModel):
+ created: float
+
+ completed: Optional[float] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Output(BaseModel):
+ cursor: int
+
+ output: str
+
+ size: int
+
+ truncated: bool
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionMessageShell(BaseModel):
+ id: SessionMessageID
+
+ command: str
+
+ shell_id: ShellID = FieldInfo(alias="shellID")
+
+ status: Literal["running", "exited", "timeout", "killed"]
+
+ time: Time
+
+ type: Literal["shell"]
+
+ exit: Union[Literal["Infinity", "-Infinity", "NaN"], float, None] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ output: Optional[Output] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_skill.py b/src/opencode/types/shared/session_message_skill.py
new file mode 100644
index 0000000..ba46ed2
--- /dev/null
+++ b/src/opencode/types/shared/session_message_skill.py
@@ -0,0 +1,57 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .skill_id import SkillID
+from ..._models import BaseModel
+from .skill_name import SkillName
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionMessageSkill", "Time"]
+
+
+class Time(BaseModel):
+ created: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionMessageSkill(BaseModel):
+ id: SessionMessageID
+
+ name: SkillName
+
+ skill: SkillID
+
+ text: str
+
+ time: Time
+
+ type: Literal["skill"]
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_synthetic.py b/src/opencode/types/shared/session_message_synthetic.py
new file mode 100644
index 0000000..8535f1f
--- /dev/null
+++ b/src/opencode/types/shared/session_message_synthetic.py
@@ -0,0 +1,53 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionMessageSynthetic", "Time"]
+
+
+class Time(BaseModel):
+ created: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionMessageSynthetic(BaseModel):
+ id: SessionMessageID
+
+ text: str
+
+ time: Time
+
+ type: Literal["synthetic"]
+
+ description: Optional[str] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_system.py b/src/opencode/types/shared/session_message_system.py
new file mode 100644
index 0000000..a2b461c
--- /dev/null
+++ b/src/opencode/types/shared/session_message_system.py
@@ -0,0 +1,53 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionMessageSystem", "Time"]
+
+
+class Time(BaseModel):
+ created: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionMessageSystem(BaseModel):
+ id: SessionMessageID
+
+ text: str
+
+ time: Time
+
+ type: Literal["system"]
+
+ description: Optional[str] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_tool_state_completed.py b/src/opencode/types/shared/session_message_tool_state_completed.py
new file mode 100644
index 0000000..ae9ab9c
--- /dev/null
+++ b/src/opencode/types/shared/session_message_tool_state_completed.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .tool_content import ToolContent
+
+__all__ = ["SessionMessageToolStateCompleted"]
+
+
+class SessionMessageToolStateCompleted(BaseModel):
+ content: List[ToolContent]
+
+ input: Dict[str, object]
+
+ status: Literal["completed"]
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_tool_state_error.py b/src/opencode/types/shared/session_message_tool_state_error.py
new file mode 100644
index 0000000..37bb7d5
--- /dev/null
+++ b/src/opencode/types/shared/session_message_tool_state_error.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .tool_content import ToolContent
+from .session_structured_error import SessionStructuredError
+
+__all__ = ["SessionMessageToolStateError"]
+
+
+class SessionMessageToolStateError(BaseModel):
+ error: SessionStructuredError
+
+ input: Dict[str, object]
+
+ status: Literal["error"]
+
+ content: Optional[List[ToolContent]] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_tool_state_running.py b/src/opencode/types/shared/session_message_tool_state_running.py
new file mode 100644
index 0000000..b0c3429
--- /dev/null
+++ b/src/opencode/types/shared/session_message_tool_state_running.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["SessionMessageToolStateRunning"]
+
+
+class SessionMessageToolStateRunning(BaseModel):
+ input: Dict[str, object]
+
+ metadata: Dict[str, object]
+
+ status: Literal["running"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_tool_state_streaming.py b/src/opencode/types/shared/session_message_tool_state_streaming.py
new file mode 100644
index 0000000..92e60ab
--- /dev/null
+++ b/src/opencode/types/shared/session_message_tool_state_streaming.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["SessionMessageToolStateStreaming"]
+
+
+class SessionMessageToolStateStreaming(BaseModel):
+ input: str
+
+ status: Literal["streaming"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_message_user.py b/src/opencode/types/shared/session_message_user.py
new file mode 100644
index 0000000..03f7a92
--- /dev/null
+++ b/src/opencode/types/shared/session_message_user.py
@@ -0,0 +1,60 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_message_id import SessionMessageID
+from .prompt_file_attachment import PromptFileAttachment
+from .prompt_agent_attachment import PromptAgentAttachment
+from .prompt_skill_attachment import PromptSkillAttachment
+
+__all__ = ["SessionMessageUser", "Time"]
+
+
+class Time(BaseModel):
+ created: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionMessageUser(BaseModel):
+ id: SessionMessageID
+
+ text: str
+
+ time: Time
+
+ type: Literal["user"]
+
+ agents: Optional[List[PromptAgentAttachment]] = None
+
+ files: Optional[List[PromptFileAttachment]] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ skills: Optional[List[PromptSkillAttachment]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_messages_response.py b/src/opencode/types/shared/session_messages_response.py
new file mode 100644
index 0000000..ea15707
--- /dev/null
+++ b/src/opencode/types/shared/session_messages_response.py
@@ -0,0 +1,46 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_message_info import SessionMessageInfo
+
+__all__ = ["SessionMessagesResponse", "Cursor"]
+
+
+class Cursor(BaseModel):
+ next: Optional[str] = None
+
+ previous: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionMessagesResponse(BaseModel):
+ cursor: Cursor
+
+ data: List[SessionMessageInfo]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_metadata.py b/src/opencode/types/shared/session_metadata.py
new file mode 100644
index 0000000..b8544c1
--- /dev/null
+++ b/src/opencode/types/shared/session_metadata.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionMetadata"]
+
+SessionMetadata: TypeAlias = Dict[str, object]
diff --git a/src/opencode/types/shared/session_metadata_updated_event.py b/src/opencode/types/shared/session_metadata_updated_event.py
new file mode 100644
index 0000000..93bfc22
--- /dev/null
+++ b/src/opencode/types/shared/session_metadata_updated_event.py
@@ -0,0 +1,82 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_metadata import SessionMetadata
+
+__all__ = ["SessionMetadataUpdatedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ metadata: SessionMetadata
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionMetadataUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.metadata.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_model_selected_event.py b/src/opencode/types/shared/session_model_selected_event.py
new file mode 100644
index 0000000..a99b647
--- /dev/null
+++ b/src/opencode/types/shared/session_model_selected_event.py
@@ -0,0 +1,84 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .model_ref import ModelRef
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+
+__all__ = ["SessionModelSelectedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ model: ModelRef
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ previous: Optional[ModelRef] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionModelSelectedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.model.selected"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_moved_event.py b/src/opencode/types/shared/session_moved_event.py
new file mode 100644
index 0000000..1b72f36
--- /dev/null
+++ b/src/opencode/types/shared/session_moved_event.py
@@ -0,0 +1,87 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .project_id import ProjectID
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .relative_path import RelativePath
+
+__all__ = ["SessionMovedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ location: LocationRef
+
+ project_id: ProjectID = FieldInfo(alias="projectID")
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ subpath: Optional[RelativePath] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionMovedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.moved"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_permissions_event.py b/src/opencode/types/shared/session_permissions_event.py
new file mode 100644
index 0000000..804bfb9
--- /dev/null
+++ b/src/opencode/types/shared/session_permissions_event.py
@@ -0,0 +1,82 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .permission_ruleset import PermissionRuleset
+
+__all__ = ["SessionPermissionsEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ permissions: PermissionRuleset
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionPermissionsEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.permissions"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_reasoning_delta_event.py b/src/opencode/types/shared/session_reasoning_delta_event.py
new file mode 100644
index 0000000..da9618c
--- /dev/null
+++ b/src/opencode/types/shared/session_reasoning_delta_event.py
@@ -0,0 +1,62 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionReasoningDeltaEvent", "Data"]
+
+
+class Data(BaseModel):
+ assistant_message_id: SessionMessageID = FieldInfo(alias="assistantMessageID")
+
+ delta: str
+
+ ordinal: int
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionReasoningDeltaEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["session.reasoning.delta"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_reasoning_ended_event.py b/src/opencode/types/shared/session_reasoning_ended_event.py
new file mode 100644
index 0000000..150345e
--- /dev/null
+++ b/src/opencode/types/shared/session_reasoning_ended_event.py
@@ -0,0 +1,89 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_message_id import SessionMessageID
+from .session_message_native9 import SessionMessageNative9
+
+__all__ = ["SessionReasoningEndedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ assistant_message_id: SessionMessageID = FieldInfo(alias="assistantMessageID")
+
+ ordinal: int
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ text: str
+
+ native: Optional[SessionMessageNative9] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionReasoningEndedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.reasoning.ended"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_reasoning_started_event.py b/src/opencode/types/shared/session_reasoning_started_event.py
new file mode 100644
index 0000000..33f6cd9
--- /dev/null
+++ b/src/opencode/types/shared/session_reasoning_started_event.py
@@ -0,0 +1,87 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_message_id import SessionMessageID
+from .session_message_native8 import SessionMessageNative8
+
+__all__ = ["SessionReasoningStartedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ assistant_message_id: SessionMessageID = FieldInfo(alias="assistantMessageID")
+
+ ordinal: int
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ native: Optional[SessionMessageNative8] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionReasoningStartedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.reasoning.started"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_renamed_event.py b/src/opencode/types/shared/session_renamed_event.py
new file mode 100644
index 0000000..5523dac
--- /dev/null
+++ b/src/opencode/types/shared/session_renamed_event.py
@@ -0,0 +1,81 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+
+__all__ = ["SessionRenamedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ title: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionRenamedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.renamed"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_retry_scheduled_event.py b/src/opencode/types/shared/session_retry_scheduled_event.py
new file mode 100644
index 0000000..3d656d1
--- /dev/null
+++ b/src/opencode/types/shared/session_retry_scheduled_event.py
@@ -0,0 +1,89 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_message_id import SessionMessageID
+from .session_structured_error import SessionStructuredError
+
+__all__ = ["SessionRetryScheduledEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ assistant_message_id: SessionMessageID = FieldInfo(alias="assistantMessageID")
+
+ at: int
+
+ attempt: int
+
+ error: SessionStructuredError
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionRetryScheduledEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.retry.scheduled"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_revert.py b/src/opencode/types/shared/session_revert.py
new file mode 100644
index 0000000..e715525
--- /dev/null
+++ b/src/opencode/types/shared/session_revert.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .snapshot_id import SnapshotID
+from .file_diff_info import FileDiffInfo
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionRevert"]
+
+
+class SessionRevert(BaseModel):
+ message_id: SessionMessageID = FieldInfo(alias="messageID")
+
+ files: Optional[List[FileDiffInfo]] = None
+
+ part_id: Optional[str] = FieldInfo(alias="partID", default=None)
+
+ snapshot: Optional[SnapshotID] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_revert_cleared_event.py b/src/opencode/types/shared/session_revert_cleared_event.py
new file mode 100644
index 0000000..6eb7f4e
--- /dev/null
+++ b/src/opencode/types/shared/session_revert_cleared_event.py
@@ -0,0 +1,79 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+
+__all__ = ["SessionRevertClearedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionRevertClearedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.revert.cleared"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_revert_committed_event.py b/src/opencode/types/shared/session_revert_committed_event.py
new file mode 100644
index 0000000..68754d7
--- /dev/null
+++ b/src/opencode/types/shared/session_revert_committed_event.py
@@ -0,0 +1,82 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionRevertCommittedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ to: SessionMessageID
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionRevertCommittedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.revert.committed"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_revert_staged_event.py b/src/opencode/types/shared/session_revert_staged_event.py
new file mode 100644
index 0000000..c22433d
--- /dev/null
+++ b/src/opencode/types/shared/session_revert_staged_event.py
@@ -0,0 +1,82 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_revert import SessionRevert
+
+__all__ = ["SessionRevertStagedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ revert: SessionRevert
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionRevertStagedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.revert.staged"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_shell_ended_event.py b/src/opencode/types/shared/session_shell_ended_event.py
new file mode 100644
index 0000000..678b420
--- /dev/null
+++ b/src/opencode/types/shared/session_shell_ended_event.py
@@ -0,0 +1,106 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .shell_info import ShellInfo
+from .location_ref import LocationRef
+from .event_version import EventVersion
+
+__all__ = ["SessionShellEndedEvent", "Data", "DataOutput", "Durable"]
+
+
+class DataOutput(BaseModel):
+ cursor: int
+
+ output: str
+
+ size: int
+
+ truncated: bool
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Data(BaseModel):
+ output: DataOutput
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ shell: ShellInfo
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionShellEndedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.shell.ended"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_shell_started_event.py b/src/opencode/types/shared/session_shell_started_event.py
new file mode 100644
index 0000000..44c93a6
--- /dev/null
+++ b/src/opencode/types/shared/session_shell_started_event.py
@@ -0,0 +1,82 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .shell_info import ShellInfo
+from .location_ref import LocationRef
+from .event_version import EventVersion
+
+__all__ = ["SessionShellStartedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ shell: ShellInfo
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionShellStartedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.shell.started"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_skill_activated_event.py b/src/opencode/types/shared/session_skill_activated_event.py
new file mode 100644
index 0000000..a2dfd11
--- /dev/null
+++ b/src/opencode/types/shared/session_skill_activated_event.py
@@ -0,0 +1,87 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from .skill_id import SkillID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .skill_name import SkillName
+from .location_ref import LocationRef
+from .event_version import EventVersion
+
+__all__ = ["SessionSkillActivatedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ id: SkillID
+
+ name: SkillName
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ text: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionSkillActivatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.skill.activated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_stats_activity.py b/src/opencode/types/shared/session_stats_activity.py
new file mode 100644
index 0000000..0228904
--- /dev/null
+++ b/src/opencode/types/shared/session_stats_activity.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["SessionStatsActivity"]
+
+
+class SessionStatsActivity(BaseModel):
+ date: str
+
+ steps: int
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_stats_info.py b/src/opencode/types/shared/session_stats_info.py
new file mode 100644
index 0000000..8ecb5ea
--- /dev/null
+++ b/src/opencode/types/shared/session_stats_info.py
@@ -0,0 +1,70 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .money_usd import MoneyUSD
+from .token_usage_info import TokenUsageInfo
+from .session_stats_tools import SessionStatsTools
+from .session_stats_activity import SessionStatsActivity
+from .session_stats_model_usage import SessionStatsModelUsage
+
+__all__ = ["SessionStatsInfo", "Range"]
+
+
+class Range(BaseModel):
+ from_: float = FieldInfo(alias="from")
+
+ to: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionStatsInfo(BaseModel):
+ active_days: int = FieldInfo(alias="activeDays")
+
+ activity: List[SessionStatsActivity]
+
+ cost: MoneyUSD
+
+ models: List[SessionStatsModelUsage]
+
+ prompts: int
+
+ range: Range
+
+ sessions: int
+
+ steps: int
+
+ streak: int
+
+ subagents: int
+
+ tokens: TokenUsageInfo
+
+ tools: SessionStatsTools
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_stats_model_usage.py b/src/opencode/types/shared/session_stats_model_usage.py
new file mode 100644
index 0000000..42c8896
--- /dev/null
+++ b/src/opencode/types/shared/session_stats_model_usage.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .model_ref import ModelRef
+from .money_usd import MoneyUSD
+from .token_usage_info import TokenUsageInfo
+
+__all__ = ["SessionStatsModelUsage"]
+
+
+class SessionStatsModelUsage(BaseModel):
+ cost: MoneyUSD
+
+ model: ModelRef
+
+ steps: int
+
+ tokens: TokenUsageInfo
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_stats_tool_totals.py b/src/opencode/types/shared/session_stats_tool_totals.py
new file mode 100644
index 0000000..61042f8
--- /dev/null
+++ b/src/opencode/types/shared/session_stats_tool_totals.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["SessionStatsToolTotals"]
+
+
+class SessionStatsToolTotals(BaseModel):
+ calls: int
+
+ failed: int
+
+ succeeded: int
+
+ unfinished: int
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_stats_tool_usage.py b/src/opencode/types/shared/session_stats_tool_usage.py
new file mode 100644
index 0000000..5c43091
--- /dev/null
+++ b/src/opencode/types/shared/session_stats_tool_usage.py
@@ -0,0 +1,35 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["SessionStatsToolUsage"]
+
+
+class SessionStatsToolUsage(BaseModel):
+ calls: int
+
+ failed: int
+
+ name: str
+
+ succeeded: int
+
+ unfinished: int
+
+ duration_p50: Optional[float] = FieldInfo(alias="durationP50", default=None)
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_stats_tools.py b/src/opencode/types/shared/session_stats_tools.py
new file mode 100644
index 0000000..e712f06
--- /dev/null
+++ b/src/opencode/types/shared/session_stats_tools.py
@@ -0,0 +1,69 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Union
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_stats_tool_usage import SessionStatsToolUsage
+from .session_stats_tool_totals import SessionStatsToolTotals
+
+__all__ = ["SessionStatsTools", "SessionStatsToolsVariant0", "SessionStatsToolsVariant1", "SessionStatsToolsVariant2"]
+
+
+class SessionStatsToolsVariant0(BaseModel):
+ mode: Literal["none"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionStatsToolsVariant1(BaseModel):
+ mode: Literal["summary"]
+
+ totals: SessionStatsToolTotals
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionStatsToolsVariant2(BaseModel):
+ mode: Literal["detail"]
+
+ totals: SessionStatsToolTotals
+
+ usage: List[SessionStatsToolUsage]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+SessionStatsTools: TypeAlias = Union[SessionStatsToolsVariant0, SessionStatsToolsVariant1, SessionStatsToolsVariant2]
diff --git a/src/opencode/types/shared/session_status.py b/src/opencode/types/shared/session_status.py
new file mode 100644
index 0000000..e4adfcf
--- /dev/null
+++ b/src/opencode/types/shared/session_status.py
@@ -0,0 +1,101 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union, Optional
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = [
+ "SessionStatus",
+ "SessionStatusVariant0",
+ "SessionStatusVariant1",
+ "SessionStatusVariant1Action",
+ "SessionStatusVariant2",
+]
+
+
+class SessionStatusVariant0(BaseModel):
+ type: Literal["idle"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionStatusVariant1Action(BaseModel):
+ label: str
+
+ message: str
+
+ provider: str
+
+ reason: str
+
+ title: str
+
+ link: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionStatusVariant1(BaseModel):
+ attempt: int
+
+ message: str
+
+ next: int
+
+ type: Literal["retry"]
+
+ action: Optional[SessionStatusVariant1Action] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionStatusVariant2(BaseModel):
+ type: Literal["busy"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+SessionStatus: TypeAlias = Union[SessionStatusVariant0, SessionStatusVariant1, SessionStatusVariant2]
diff --git a/src/opencode/types/shared/session_status_updated.py b/src/opencode/types/shared/session_status_updated.py
new file mode 100644
index 0000000..8dc2264
--- /dev/null
+++ b/src/opencode/types/shared/session_status_updated.py
@@ -0,0 +1,58 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .session_status import SessionStatus
+
+__all__ = ["SessionStatusUpdated", "Data"]
+
+
+class Data(BaseModel):
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ status: SessionStatus
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionStatusUpdated(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["session.status"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_step_ended_event.py b/src/opencode/types/shared/session_step_ended_event.py
new file mode 100644
index 0000000..8905b7c
--- /dev/null
+++ b/src/opencode/types/shared/session_step_ended_event.py
@@ -0,0 +1,101 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .money_usd import MoneyUSD
+from .session_id import SessionID
+from .snapshot_id import SnapshotID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .relative_path import RelativePath
+from .token_usage_info import TokenUsageInfo
+from .session_message_id import SessionMessageID
+from .session_message_native5 import SessionMessageNative5
+
+__all__ = ["SessionStepEndedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ assistant_message_id: SessionMessageID = FieldInfo(alias="assistantMessageID")
+
+ cost: MoneyUSD
+
+ finish: Literal["stop", "length", "tool-calls", "content-filter", "error", "unknown"]
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ tokens: TokenUsageInfo
+
+ files: Optional[List[RelativePath]] = None
+
+ native: Optional[SessionMessageNative5] = None
+
+ raw_finish: Optional[str] = FieldInfo(alias="rawFinish", default=None)
+
+ snapshot: Optional[SnapshotID] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionStepEndedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.step.ended"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_step_failed_event.py b/src/opencode/types/shared/session_step_failed_event.py
new file mode 100644
index 0000000..f971b07
--- /dev/null
+++ b/src/opencode/types/shared/session_step_failed_event.py
@@ -0,0 +1,104 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .money_usd import MoneyUSD
+from .session_id import SessionID
+from .snapshot_id import SnapshotID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .relative_path import RelativePath
+from .token_usage_info import TokenUsageInfo
+from .session_message_id import SessionMessageID
+from .session_message_native6 import SessionMessageNative6
+from .session_structured_error import SessionStructuredError
+
+__all__ = ["SessionStepFailedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ assistant_message_id: SessionMessageID = FieldInfo(alias="assistantMessageID")
+
+ error: SessionStructuredError
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ cost: Optional[MoneyUSD] = None
+
+ files: Optional[List[RelativePath]] = None
+
+ finish: Optional[Literal["content-filter"]] = None
+
+ native: Optional[SessionMessageNative6] = None
+
+ raw_finish: Optional[str] = FieldInfo(alias="rawFinish", default=None)
+
+ snapshot: Optional[SnapshotID] = None
+
+ tokens: Optional[TokenUsageInfo] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionStepFailedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.step.failed"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_step_started_event.py b/src/opencode/types/shared/session_step_started_event.py
new file mode 100644
index 0000000..3156022
--- /dev/null
+++ b/src/opencode/types/shared/session_step_started_event.py
@@ -0,0 +1,93 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .agent_id import AgentID
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .model_ref import ModelRef
+from .session_id import SessionID
+from .snapshot_id import SnapshotID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionStepStartedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ agent: AgentID
+
+ assistant_message_id: SessionMessageID = FieldInfo(alias="assistantMessageID")
+
+ model: ModelRef
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ started: int
+
+ snapshot: Optional[SnapshotID] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionStepStartedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.step.started"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_step_streamed_event.py b/src/opencode/types/shared/session_step_streamed_event.py
new file mode 100644
index 0000000..ee40660
--- /dev/null
+++ b/src/opencode/types/shared/session_step_streamed_event.py
@@ -0,0 +1,82 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionStepStreamedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ assistant_message_id: SessionMessageID = FieldInfo(alias="assistantMessageID")
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionStepStreamedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.step.streamed"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_structured_error.py b/src/opencode/types/shared/session_structured_error.py
new file mode 100644
index 0000000..0542120
--- /dev/null
+++ b/src/opencode/types/shared/session_structured_error.py
@@ -0,0 +1,49 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["SessionStructuredError", "Response"]
+
+
+class Response(BaseModel):
+ body: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionStructuredError(BaseModel):
+ message: str
+
+ type: str
+
+ response: Optional[Response] = None
+
+ retry_after_ms: Optional[int] = FieldInfo(alias="retryAfterMs", default=None)
+
+ status: Optional[int] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_synthetic_event.py b/src/opencode/types/shared/session_synthetic_event.py
new file mode 100644
index 0000000..8dbb29d
--- /dev/null
+++ b/src/opencode/types/shared/session_synthetic_event.py
@@ -0,0 +1,85 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+
+__all__ = ["SessionSyntheticEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ text: str
+
+ description: Optional[str] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionSyntheticEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.synthetic"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_text_delta_event.py b/src/opencode/types/shared/session_text_delta_event.py
new file mode 100644
index 0000000..f2f9201
--- /dev/null
+++ b/src/opencode/types/shared/session_text_delta_event.py
@@ -0,0 +1,62 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionTextDeltaEvent", "Data"]
+
+
+class Data(BaseModel):
+ assistant_message_id: SessionMessageID = FieldInfo(alias="assistantMessageID")
+
+ delta: str
+
+ ordinal: int
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionTextDeltaEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["session.text.delta"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_text_ended_event.py b/src/opencode/types/shared/session_text_ended_event.py
new file mode 100644
index 0000000..4fe8d48
--- /dev/null
+++ b/src/opencode/types/shared/session_text_ended_event.py
@@ -0,0 +1,89 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_message_id import SessionMessageID
+from .session_message_native7 import SessionMessageNative7
+
+__all__ = ["SessionTextEndedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ assistant_message_id: SessionMessageID = FieldInfo(alias="assistantMessageID")
+
+ ordinal: int
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ text: str
+
+ native: Optional[SessionMessageNative7] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionTextEndedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.text.ended"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_text_started_event.py b/src/opencode/types/shared/session_text_started_event.py
new file mode 100644
index 0000000..42a0f88
--- /dev/null
+++ b/src/opencode/types/shared/session_text_started_event.py
@@ -0,0 +1,84 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionTextStartedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ assistant_message_id: SessionMessageID = FieldInfo(alias="assistantMessageID")
+
+ ordinal: int
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionTextStartedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.text.started"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_tool_called_event.py b/src/opencode/types/shared/session_tool_called_event.py
new file mode 100644
index 0000000..94852ef
--- /dev/null
+++ b/src/opencode/types/shared/session_tool_called_event.py
@@ -0,0 +1,91 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_message_id import SessionMessageID
+from .session_message_native10 import SessionMessageNative10
+
+__all__ = ["SessionToolCalledEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ id: str
+
+ assistant_message_id: SessionMessageID = FieldInfo(alias="assistantMessageID")
+
+ executed: bool
+
+ input: Dict[str, object]
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ native: Optional[SessionMessageNative10] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionToolCalledEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.tool.called"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_tool_failed_event.py b/src/opencode/types/shared/session_tool_failed_event.py
new file mode 100644
index 0000000..2aed38a
--- /dev/null
+++ b/src/opencode/types/shared/session_tool_failed_event.py
@@ -0,0 +1,97 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .tool_content import ToolContent
+from .event_version10 import EventVersion10
+from .session_message_id import SessionMessageID
+from .session_message_native12 import SessionMessageNative12
+from .session_structured_error import SessionStructuredError
+
+__all__ = ["SessionToolFailedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ id: str
+
+ assistant_message_id: SessionMessageID = FieldInfo(alias="assistantMessageID")
+
+ error: SessionStructuredError
+
+ executed: bool
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ content: Optional[List[ToolContent]] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ native: Optional[SessionMessageNative12] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion10
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionToolFailedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.tool.failed"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_tool_input_delta_event.py b/src/opencode/types/shared/session_tool_input_delta_event.py
new file mode 100644
index 0000000..1ef4d5c
--- /dev/null
+++ b/src/opencode/types/shared/session_tool_input_delta_event.py
@@ -0,0 +1,62 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionToolInputDeltaEvent", "Data"]
+
+
+class Data(BaseModel):
+ id: str
+
+ assistant_message_id: SessionMessageID = FieldInfo(alias="assistantMessageID")
+
+ delta: str
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionToolInputDeltaEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["session.tool.input.delta"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_tool_input_ended_event.py b/src/opencode/types/shared/session_tool_input_ended_event.py
new file mode 100644
index 0000000..077b736
--- /dev/null
+++ b/src/opencode/types/shared/session_tool_input_ended_event.py
@@ -0,0 +1,86 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionToolInputEndedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ id: str
+
+ assistant_message_id: SessionMessageID = FieldInfo(alias="assistantMessageID")
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ text: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionToolInputEndedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.tool.input.ended"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_tool_input_started_event.py b/src/opencode/types/shared/session_tool_input_started_event.py
new file mode 100644
index 0000000..c19ed3c
--- /dev/null
+++ b/src/opencode/types/shared/session_tool_input_started_event.py
@@ -0,0 +1,86 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionToolInputStartedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ id: str
+
+ assistant_message_id: SessionMessageID = FieldInfo(alias="assistantMessageID")
+
+ name: str
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionToolInputStartedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.tool.input.started"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_tool_progress_event.py b/src/opencode/types/shared/session_tool_progress_event.py
new file mode 100644
index 0000000..97e92eb
--- /dev/null
+++ b/src/opencode/types/shared/session_tool_progress_event.py
@@ -0,0 +1,62 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionToolProgressEvent", "Data"]
+
+
+class Data(BaseModel):
+ id: str
+
+ assistant_message_id: SessionMessageID = FieldInfo(alias="assistantMessageID")
+
+ metadata: Dict[str, object]
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionToolProgressEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["session.tool.progress"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_tool_success_event.py b/src/opencode/types/shared/session_tool_success_event.py
new file mode 100644
index 0000000..0f4e91f
--- /dev/null
+++ b/src/opencode/types/shared/session_tool_success_event.py
@@ -0,0 +1,94 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .tool_content import ToolContent
+from .event_version10 import EventVersion10
+from .session_message_id import SessionMessageID
+from .session_message_native11 import SessionMessageNative11
+
+__all__ = ["SessionToolSuccessEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ id: str
+
+ assistant_message_id: SessionMessageID = FieldInfo(alias="assistantMessageID")
+
+ content: List[ToolContent]
+
+ executed: bool
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ metadata: Optional[Dict[str, object]] = None
+
+ native: Optional[SessionMessageNative11] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion10
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionToolSuccessEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.tool.success"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_transfer_data.py b/src/opencode/types/shared/session_transfer_data.py
new file mode 100644
index 0000000..6b7ef42
--- /dev/null
+++ b/src/opencode/types/shared/session_transfer_data.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_info import SessionInfo
+from .session_message_info import SessionMessageInfo
+
+__all__ = ["SessionTransferData"]
+
+
+class SessionTransferData(BaseModel):
+ info: SessionInfo
+
+ messages: List[SessionMessageInfo]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_unarchived_event.py b/src/opencode/types/shared/session_unarchived_event.py
new file mode 100644
index 0000000..b4b2b26
--- /dev/null
+++ b/src/opencode/types/shared/session_unarchived_event.py
@@ -0,0 +1,79 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+
+__all__ = ["SessionUnarchivedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionUnarchivedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.unarchived"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_usage_updated_event.py b/src/opencode/types/shared/session_usage_updated_event.py
new file mode 100644
index 0000000..8cf6d33
--- /dev/null
+++ b/src/opencode/types/shared/session_usage_updated_event.py
@@ -0,0 +1,61 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .money_usd import MoneyUSD
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .token_usage_info import TokenUsageInfo
+
+__all__ = ["SessionUsageUpdatedEvent", "Data"]
+
+
+class Data(BaseModel):
+ cost: MoneyUSD
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ tokens: TokenUsageInfo
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionUsageUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["session.usage.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_viewed_event.py b/src/opencode/types/shared/session_viewed_event.py
new file mode 100644
index 0000000..3425d83
--- /dev/null
+++ b/src/opencode/types/shared/session_viewed_event.py
@@ -0,0 +1,81 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .session_id import SessionID
+from .location_ref import LocationRef
+from .event_version import EventVersion
+
+__all__ = ["SessionViewedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ idle: float
+
+ session_id: SessionID = FieldInfo(alias="sessionID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionViewedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["session.viewed"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/sessions_cursor.py b/src/opencode/types/shared/sessions_cursor.py
new file mode 100644
index 0000000..c19e245
--- /dev/null
+++ b/src/opencode/types/shared/sessions_cursor.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionsCursor"]
+
+SessionsCursor: TypeAlias = str
diff --git a/src/opencode/types/shared/sessions_response.py b/src/opencode/types/shared/sessions_response.py
new file mode 100644
index 0000000..df5de93
--- /dev/null
+++ b/src/opencode/types/shared/sessions_response.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .session_info import SessionInfo
+from .sessions_cursor import SessionsCursor
+
+__all__ = ["SessionsResponse", "Cursor"]
+
+
+class Cursor(BaseModel):
+ next: Optional[SessionsCursor] = None
+
+ previous: Optional[SessionsCursor] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class SessionsResponse(BaseModel):
+ cursor: Cursor
+
+ data: List[SessionInfo]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/shell_created_event.py b/src/opencode/types/shared/shell_created_event.py
new file mode 100644
index 0000000..8b42d10
--- /dev/null
+++ b/src/opencode/types/shared/shell_created_event.py
@@ -0,0 +1,55 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .shell_info import ShellInfo
+from .location_ref import LocationRef
+
+__all__ = ["ShellCreatedEvent", "Data"]
+
+
+class Data(BaseModel):
+ info: ShellInfo
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class ShellCreatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["shell.created"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/shell_deleted_event.py b/src/opencode/types/shared/shell_deleted_event.py
new file mode 100644
index 0000000..0c00612
--- /dev/null
+++ b/src/opencode/types/shared/shell_deleted_event.py
@@ -0,0 +1,55 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from .shell_id import ShellID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["ShellDeletedEvent", "Data"]
+
+
+class Data(BaseModel):
+ id: ShellID
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class ShellDeletedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["shell.deleted"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/shell_exited_event.py b/src/opencode/types/shared/shell_exited_event.py
new file mode 100644
index 0000000..bc4e74d
--- /dev/null
+++ b/src/opencode/types/shared/shell_exited_event.py
@@ -0,0 +1,59 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from .shell_id import ShellID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["ShellExitedEvent", "Data"]
+
+
+class Data(BaseModel):
+ id: ShellID
+
+ status: Literal["running", "exited", "timeout", "killed"]
+
+ exit: Optional[float] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class ShellExitedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["shell.exited"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/shell_id.py b/src/opencode/types/shared/shell_id.py
new file mode 100644
index 0000000..7fd3e47
--- /dev/null
+++ b/src/opencode/types/shared/shell_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["ShellID"]
+
+ShellID: TypeAlias = str
diff --git a/src/opencode/types/shared/shell_info.py b/src/opencode/types/shared/shell_info.py
new file mode 100644
index 0000000..fbe7e2b
--- /dev/null
+++ b/src/opencode/types/shared/shell_info.py
@@ -0,0 +1,66 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .shell_id import ShellID
+from ..._models import BaseModel
+
+__all__ = ["ShellInfo", "Time"]
+
+
+class Time(BaseModel):
+ started: float
+ """Start time in milliseconds since the Unix epoch"""
+
+ completed: Optional[float] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class ShellInfo(BaseModel):
+ id: ShellID
+
+ command: str
+
+ cwd: str
+
+ file: str
+
+ metadata: Dict[str, object]
+
+ shell: str
+
+ status: Literal["running", "exited", "timeout", "killed"]
+
+ time: Time
+
+ exit: Optional[float] = None
+
+ pid: Optional[int] = None
+
+ signal: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/skill_id.py b/src/opencode/types/shared/skill_id.py
new file mode 100644
index 0000000..ce76e65
--- /dev/null
+++ b/src/opencode/types/shared/skill_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["SkillID"]
+
+SkillID: TypeAlias = str
diff --git a/src/opencode/types/shared/skill_info.py b/src/opencode/types/shared/skill_info.py
new file mode 100644
index 0000000..fa28628
--- /dev/null
+++ b/src/opencode/types/shared/skill_info.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from .skill_id import SkillID
+from ..._models import BaseModel
+from .skill_name import SkillName
+from .absolute_path import AbsolutePath
+
+__all__ = ["SkillInfo"]
+
+
+class SkillInfo(BaseModel):
+ id: SkillID
+
+ content: str
+
+ name: SkillName
+
+ path: AbsolutePath
+
+ autoinvoke: Optional[bool] = None
+
+ description: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/skill_name.py b/src/opencode/types/shared/skill_name.py
new file mode 100644
index 0000000..3504a12
--- /dev/null
+++ b/src/opencode/types/shared/skill_name.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["SkillName"]
+
+SkillName: TypeAlias = str
diff --git a/src/opencode/types/shared/skill_updated_event.py b/src/opencode/types/shared/skill_updated_event.py
new file mode 100644
index 0000000..08eabcb
--- /dev/null
+++ b/src/opencode/types/shared/skill_updated_event.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["SkillUpdatedEvent"]
+
+
+class SkillUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: object
+
+ type: Literal["skill.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/snapshot_id.py b/src/opencode/types/shared/snapshot_id.py
new file mode 100644
index 0000000..9a6756d
--- /dev/null
+++ b/src/opencode/types/shared/snapshot_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["SnapshotID"]
+
+SnapshotID: TypeAlias = str
diff --git a/src/opencode/types/shared/token_usage_info.py b/src/opencode/types/shared/token_usage_info.py
new file mode 100644
index 0000000..a707aed
--- /dev/null
+++ b/src/opencode/types/shared/token_usage_info.py
@@ -0,0 +1,49 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["TokenUsageInfo", "Cache"]
+
+
+class Cache(BaseModel):
+ read: float
+
+ write: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class TokenUsageInfo(BaseModel):
+ cache: Cache
+
+ input: float
+
+ output: float
+
+ reasoning: float
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/tool_content.py b/src/opencode/types/shared/tool_content.py
new file mode 100644
index 0000000..92e6020
--- /dev/null
+++ b/src/opencode/types/shared/tool_content.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import TypeAlias
+
+from .tool_file_content import ToolFileContent
+from .tool_text_content import ToolTextContent
+
+__all__ = ["ToolContent"]
+
+ToolContent: TypeAlias = Union[ToolTextContent, ToolFileContent]
diff --git a/src/opencode/types/shared/tool_file_content.py b/src/opencode/types/shared/tool_file_content.py
new file mode 100644
index 0000000..68415b0
--- /dev/null
+++ b/src/opencode/types/shared/tool_file_content.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ToolFileContent"]
+
+
+class ToolFileContent(BaseModel):
+ mime: str
+
+ type: Literal["file"]
+
+ uri: str
+
+ name: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/tool_text_content.py b/src/opencode/types/shared/tool_text_content.py
new file mode 100644
index 0000000..d31183d
--- /dev/null
+++ b/src/opencode/types/shared/tool_text_content.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ToolTextContent"]
+
+
+class ToolTextContent(BaseModel):
+ text: str
+
+ type: Literal["text"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/tui_command_execute_event.py b/src/opencode/types/shared/tui_command_execute_event.py
new file mode 100644
index 0000000..4bce791
--- /dev/null
+++ b/src/opencode/types/shared/tui_command_execute_event.py
@@ -0,0 +1,74 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["TuiCommandExecuteEvent", "Data"]
+
+
+class Data(BaseModel):
+ command: Union[
+ Literal[
+ "session.list",
+ "session.new",
+ "session.interrupt",
+ "session.background",
+ "session.compact",
+ "session.page.up",
+ "session.page.down",
+ "session.line.up",
+ "session.line.down",
+ "session.half.page.up",
+ "session.half.page.down",
+ "session.first",
+ "session.last",
+ "prompt.clear",
+ "prompt.submit",
+ "agent.cycle",
+ ],
+ str,
+ ]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class TuiCommandExecuteEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["tui.command.execute"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/tui_prompt_append_event.py b/src/opencode/types/shared/tui_prompt_append_event.py
new file mode 100644
index 0000000..f93e471
--- /dev/null
+++ b/src/opencode/types/shared/tui_prompt_append_event.py
@@ -0,0 +1,54 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["TuiPromptAppendEvent", "Data"]
+
+
+class Data(BaseModel):
+ text: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class TuiPromptAppendEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["tui.prompt.append"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/tui_session_select_event.py b/src/opencode/types/shared/tui_session_select_event.py
new file mode 100644
index 0000000..7d54003
--- /dev/null
+++ b/src/opencode/types/shared/tui_session_select_event.py
@@ -0,0 +1,56 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .session_id1 import SessionID1
+from .location_ref import LocationRef
+
+__all__ = ["TuiSessionSelectEvent", "Data"]
+
+
+class Data(BaseModel):
+ session_id: SessionID1 = FieldInfo(alias="sessionID")
+ """Session ID to navigate to"""
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class TuiSessionSelectEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["tui.session.select"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/tui_toast_show_event.py b/src/opencode/types/shared/tui_toast_show_event.py
new file mode 100644
index 0000000..08f1eaf
--- /dev/null
+++ b/src/opencode/types/shared/tui_toast_show_event.py
@@ -0,0 +1,60 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["TuiToastShowEvent", "Data"]
+
+
+class Data(BaseModel):
+ message: str
+
+ variant: Literal["info", "success", "warning", "error"]
+
+ duration: Optional[int] = None
+
+ title: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class TuiToastShowEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["tui.toast.show"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/vcs_base.py b/src/opencode/types/shared/vcs_base.py
new file mode 100644
index 0000000..6253755
--- /dev/null
+++ b/src/opencode/types/shared/vcs_base.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["VcsBase"]
+
+
+class VcsBase(BaseModel):
+ name: str
+
+ ref: str
+
+ source: Literal["reflog", "default"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/vcs_branch.py b/src/opencode/types/shared/vcs_branch.py
new file mode 100644
index 0000000..170d70d
--- /dev/null
+++ b/src/opencode/types/shared/vcs_branch.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["VcsBranch"]
+
+
+class VcsBranch(BaseModel):
+ current: Optional[str] = None
+
+ default: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/vcs_branch_list.py b/src/opencode/types/shared/vcs_branch_list.py
new file mode 100644
index 0000000..f3a8c94
--- /dev/null
+++ b/src/opencode/types/shared/vcs_branch_list.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+__all__ = ["VcsBranchList"]
+
+VcsBranchList: TypeAlias = List[str]
diff --git a/src/opencode/types/shared/vcs_branch_updated_event.py b/src/opencode/types/shared/vcs_branch_updated_event.py
new file mode 100644
index 0000000..37bfdd9
--- /dev/null
+++ b/src/opencode/types/shared/vcs_branch_updated_event.py
@@ -0,0 +1,54 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["VcsBranchUpdatedEvent", "Data"]
+
+
+class Data(BaseModel):
+ branch: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class VcsBranchUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["vcs.branch.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/vcs_file_status.py b/src/opencode/types/shared/vcs_file_status.py
new file mode 100644
index 0000000..43ecac3
--- /dev/null
+++ b/src/opencode/types/shared/vcs_file_status.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["VcsFileStatus"]
+
+
+class VcsFileStatus(BaseModel):
+ additions: int
+
+ deletions: int
+
+ file: str
+
+ status: Literal["added", "deleted", "modified"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/vcs_info.py b/src/opencode/types/shared/vcs_info.py
new file mode 100644
index 0000000..3155f86
--- /dev/null
+++ b/src/opencode/types/shared/vcs_info.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .vcs_branch import VcsBranch
+
+__all__ = ["VcsInfo"]
+
+
+class VcsInfo(BaseModel):
+ branch: VcsBranch
+
+ provider: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/vcs_mode.py b/src/opencode/types/shared/vcs_mode.py
new file mode 100644
index 0000000..0b00a41
--- /dev/null
+++ b/src/opencode/types/shared/vcs_mode.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["VcsMode"]
+
+VcsMode: TypeAlias = Literal["working", "branch", "committed"]
diff --git a/src/opencode/types/shared/voice_session_response.py b/src/opencode/types/shared/voice_session_response.py
new file mode 100644
index 0000000..b4e3f1b
--- /dev/null
+++ b/src/opencode/types/shared/voice_session_response.py
@@ -0,0 +1,42 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .voice_transport import VoiceTransport
+
+__all__ = ["VoiceSessionResponse", "Data"]
+
+
+class Data(BaseModel):
+ transport: VoiceTransport
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class VoiceSessionResponse(BaseModel):
+ data: Data
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/voice_transport.py b/src/opencode/types/shared/voice_transport.py
new file mode 100644
index 0000000..0423ec0
--- /dev/null
+++ b/src/opencode/types/shared/voice_transport.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import TypeAlias
+
+from .voice_web_rtc_transport import VoiceWebRTCTransport
+from .voice_web_socket_transport import VoiceWebSocketTransport
+
+__all__ = ["VoiceTransport"]
+
+VoiceTransport: TypeAlias = Union[VoiceWebRTCTransport, VoiceWebSocketTransport]
diff --git a/src/opencode/types/shared/voice_transport_input.py b/src/opencode/types/shared/voice_transport_input.py
new file mode 100644
index 0000000..5e87192
--- /dev/null
+++ b/src/opencode/types/shared/voice_transport_input.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Union
+from typing_extensions import TypeAlias
+
+from .voice_web_rtc_transport_input import VoiceWebRTCTransportInput
+from .voice_web_socket_transport_input import VoiceWebSocketTransportInput
+
+__all__ = ["VoiceTransportInput"]
+
+VoiceTransportInput: TypeAlias = Union[VoiceWebRTCTransportInput, VoiceWebSocketTransportInput]
diff --git a/src/opencode/types/shared/voice_web_rtc_transport.py b/src/opencode/types/shared/voice_web_rtc_transport.py
new file mode 100644
index 0000000..0285c0e
--- /dev/null
+++ b/src/opencode/types/shared/voice_web_rtc_transport.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["VoiceWebRTCTransport"]
+
+
+class VoiceWebRTCTransport(BaseModel):
+ sdp: str
+
+ type: Literal["webrtc"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/voice_web_rtc_transport_input.py b/src/opencode/types/shared/voice_web_rtc_transport_input.py
new file mode 100644
index 0000000..e6f231f
--- /dev/null
+++ b/src/opencode/types/shared/voice_web_rtc_transport_input.py
@@ -0,0 +1,30 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["VoiceWebRTCTransportInput"]
+
+
+class VoiceWebRTCTransportInput(BaseModel):
+ sdp: str
+
+ type: Literal["webrtc"]
+
+ dcid: Union[Literal["Infinity", "-Infinity", "NaN"], float, None] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/voice_web_socket_transport.py b/src/opencode/types/shared/voice_web_socket_transport.py
new file mode 100644
index 0000000..0bdd671
--- /dev/null
+++ b/src/opencode/types/shared/voice_web_socket_transport.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Union
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["VoiceWebSocketTransport"]
+
+
+class VoiceWebSocketTransport(BaseModel):
+ expires_at: Union[Literal["Infinity", "-Infinity", "NaN"], float] = FieldInfo(alias="expiresAt")
+
+ token: str
+
+ type: Literal["websocket"]
+
+ url: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/voice_web_socket_transport_input.py b/src/opencode/types/shared/voice_web_socket_transport_input.py
new file mode 100644
index 0000000..b495c86
--- /dev/null
+++ b/src/opencode/types/shared/voice_web_socket_transport_input.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["VoiceWebSocketTransportInput"]
+
+
+class VoiceWebSocketTransportInput(BaseModel):
+ type: Literal["websocket"]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/web_search_id.py b/src/opencode/types/shared/web_search_id.py
new file mode 100644
index 0000000..09403c8
--- /dev/null
+++ b/src/opencode/types/shared/web_search_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["WebSearchID"]
+
+WebSearchID: TypeAlias = str
diff --git a/src/opencode/types/shared/web_search_provider.py b/src/opencode/types/shared/web_search_provider.py
new file mode 100644
index 0000000..1e54d62
--- /dev/null
+++ b/src/opencode/types/shared/web_search_provider.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .web_search_id import WebSearchID
+
+__all__ = ["WebSearchProvider"]
+
+
+class WebSearchProvider(BaseModel):
+ id: WebSearchID
+
+ name: str
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/web_search_response.py b/src/opencode/types/shared/web_search_response.py
new file mode 100644
index 0000000..cdf9709
--- /dev/null
+++ b/src/opencode/types/shared/web_search_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .web_search_id import WebSearchID
+from .web_search_result import WebSearchResult
+
+__all__ = ["WebSearchResponse"]
+
+
+class WebSearchResponse(BaseModel):
+ provider_id: WebSearchID = FieldInfo(alias="providerID")
+
+ results: List[WebSearchResult]
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/web_search_result.py b/src/opencode/types/shared/web_search_result.py
new file mode 100644
index 0000000..d4a84d4
--- /dev/null
+++ b/src/opencode/types/shared/web_search_result.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["WebSearchResult", "Time"]
+
+
+class Time(BaseModel):
+ published: Optional[float] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class WebSearchResult(BaseModel):
+ time: Time
+
+ url: str
+
+ content: Optional[str] = None
+
+ title: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/websearch_updated_event.py b/src/opencode/types/shared/websearch_updated_event.py
new file mode 100644
index 0000000..c9ff3be
--- /dev/null
+++ b/src/opencode/types/shared/websearch_updated_event.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .location_ref import LocationRef
+
+__all__ = ["WebsearchUpdatedEvent"]
+
+
+class WebsearchUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: object
+
+ type: Literal["websearch.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/workspace_id.py b/src/opencode/types/shared/workspace_id.py
new file mode 100644
index 0000000..345bf45
--- /dev/null
+++ b/src/opencode/types/shared/workspace_id.py
@@ -0,0 +1,7 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing_extensions import TypeAlias
+
+__all__ = ["WorkspaceID"]
+
+WorkspaceID: TypeAlias = str
diff --git a/src/opencode/types/shared/worktree_directory.py b/src/opencode/types/shared/worktree_directory.py
new file mode 100644
index 0000000..3508bce
--- /dev/null
+++ b/src/opencode/types/shared/worktree_directory.py
@@ -0,0 +1,28 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .absolute_path import AbsolutePath
+
+__all__ = ["WorktreeDirectory"]
+
+
+class WorktreeDirectory(BaseModel):
+ directory: AbsolutePath
+
+ strategy: Optional[str] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/worktree_info.py b/src/opencode/types/shared/worktree_info.py
new file mode 100644
index 0000000..148518c
--- /dev/null
+++ b/src/opencode/types/shared/worktree_info.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .absolute_path import AbsolutePath
+
+__all__ = ["WorktreeInfo"]
+
+
+class WorktreeInfo(BaseModel):
+ directory: AbsolutePath
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/worktree_list.py b/src/opencode/types/shared/worktree_list.py
new file mode 100644
index 0000000..543f90f
--- /dev/null
+++ b/src/opencode/types/shared/worktree_list.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+from .worktree_directory import WorktreeDirectory
+
+__all__ = ["WorktreeList"]
+
+WorktreeList: TypeAlias = List[WorktreeDirectory]
diff --git a/src/opencode/types/shared/worktree_resolved_event.py b/src/opencode/types/shared/worktree_resolved_event.py
new file mode 100644
index 0000000..f6fa235
--- /dev/null
+++ b/src/opencode/types/shared/worktree_resolved_event.py
@@ -0,0 +1,86 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .event_seq import EventSeq
+from .project_id import ProjectID
+from .location_ref import LocationRef
+from .absolute_path import AbsolutePath
+from .event_version import EventVersion
+
+__all__ = ["WorktreeResolvedEvent", "Data", "Durable"]
+
+
+class Data(BaseModel):
+ directory: AbsolutePath
+
+ previous: ProjectID
+
+ project_id: ProjectID = FieldInfo(alias="projectID")
+
+ adopted: Optional[List[ProjectID]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class Durable(BaseModel):
+ aggregate_id: str = FieldInfo(alias="aggregateID")
+
+ seq: EventSeq
+
+ version: EventVersion
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class WorktreeResolvedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ durable: Durable
+
+ type: Literal["worktree.resolved"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/worktree_updated_event.py b/src/opencode/types/shared/worktree_updated_event.py
new file mode 100644
index 0000000..621f963
--- /dev/null
+++ b/src/opencode/types/shared/worktree_updated_event.py
@@ -0,0 +1,55 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .event_id import EventID
+from ..._models import BaseModel
+from .project_id import ProjectID
+from .location_ref import LocationRef
+
+__all__ = ["WorktreeUpdatedEvent", "Data"]
+
+
+class Data(BaseModel):
+ project_id: ProjectID = FieldInfo(alias="projectID")
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class WorktreeUpdatedEvent(BaseModel):
+ id: EventID
+
+ created: float
+
+ data: Data
+
+ type: Literal["worktree.updated"]
+
+ location: Optional[LocationRef] = None
+
+ metadata: Optional[Dict[str, object]] = None
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shell_create_params.py b/src/opencode/types/shell_create_params.py
new file mode 100644
index 0000000..d1407e2
--- /dev/null
+++ b/src/opencode/types/shell_create_params.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Required, Annotated, TypeAlias, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["ShellCreateParams", "Location"]
+
+
+class ShellCreateParams(TypedDict, total=False):
+ command: Required[str]
+
+ cwd: str
+
+ location: Optional[Location]
+
+ metadata: Dict[str, object]
+
+ api_timeout: Annotated[int, PropertyInfo(alias="timeout")]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/shell_create_response.py b/src/opencode/types/shell_create_response.py
new file mode 100644
index 0000000..ca6a504
--- /dev/null
+++ b/src/opencode/types/shell_create_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.shell_info import ShellInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["ShellCreateResponse"]
+
+
+class ShellCreateResponse(BaseModel):
+ data: ShellInfo
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shell_get_params.py b/src/opencode/types/shell_get_params.py
new file mode 100644
index 0000000..a7b54be
--- /dev/null
+++ b/src/opencode/types/shell_get_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["ShellGetParams", "Location"]
+
+
+class ShellGetParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/shell_get_response.py b/src/opencode/types/shell_get_response.py
new file mode 100644
index 0000000..2cf6e18
--- /dev/null
+++ b/src/opencode/types/shell_get_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.shell_info import ShellInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["ShellGetResponse"]
+
+
+class ShellGetResponse(BaseModel):
+ data: ShellInfo
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shell_list_params.py b/src/opencode/types/shell_list_params.py
new file mode 100644
index 0000000..06c1fda
--- /dev/null
+++ b/src/opencode/types/shell_list_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["ShellListParams", "Location"]
+
+
+class ShellListParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/shell_list_response.py b/src/opencode/types/shell_list_response.py
new file mode 100644
index 0000000..ea4af20
--- /dev/null
+++ b/src/opencode/types/shell_list_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.shell_info import ShellInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["ShellListResponse"]
+
+
+class ShellListResponse(BaseModel):
+ data: List[ShellInfo]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shell_output_params.py b/src/opencode/types/shell_output_params.py
new file mode 100644
index 0000000..60059ce
--- /dev/null
+++ b/src/opencode/types/shell_output_params.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["ShellOutputParams", "Location"]
+
+
+class ShellOutputParams(TypedDict, total=False):
+ cursor: Optional[int]
+
+ limit: Optional[int]
+
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/shell_output_response.py b/src/opencode/types/shell_output_response.py
new file mode 100644
index 0000000..d3f2cee
--- /dev/null
+++ b/src/opencode/types/shell_output_response.py
@@ -0,0 +1,50 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["ShellOutputResponse", "ShellOutputResponseData"]
+
+
+class ShellOutputResponseData(BaseModel):
+ cursor: int
+
+ output: str
+
+ size: int
+
+ truncated: bool
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
+
+
+class ShellOutputResponse(BaseModel):
+ data: ShellOutputResponseData
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shell_remove_params.py b/src/opencode/types/shell_remove_params.py
new file mode 100644
index 0000000..cdef8ee
--- /dev/null
+++ b/src/opencode/types/shell_remove_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["ShellRemoveParams", "Location"]
+
+
+class ShellRemoveParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/skill_list_params.py b/src/opencode/types/skill_list_params.py
new file mode 100644
index 0000000..3ed2447
--- /dev/null
+++ b/src/opencode/types/skill_list_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["SkillListParams", "Location"]
+
+
+class SkillListParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/skill_list_response.py b/src/opencode/types/skill_list_response.py
new file mode 100644
index 0000000..51b67a1
--- /dev/null
+++ b/src/opencode/types/skill_list_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.skill_info import SkillInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["SkillListResponse"]
+
+
+class SkillListResponse(BaseModel):
+ data: List[SkillInfo]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/vc_base_params.py b/src/opencode/types/vc_base_params.py
new file mode 100644
index 0000000..97ba50b
--- /dev/null
+++ b/src/opencode/types/vc_base_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["VcBaseParams", "Location"]
+
+
+class VcBaseParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/vc_base_response.py b/src/opencode/types/vc_base_response.py
new file mode 100644
index 0000000..751da6f
--- /dev/null
+++ b/src/opencode/types/vc_base_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.vcs_base import VcsBase
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["VcBaseResponse"]
+
+
+class VcBaseResponse(BaseModel):
+ data: Optional[VcsBase] = None
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/vc_diff_params.py b/src/opencode/types/vc_diff_params.py
new file mode 100644
index 0000000..7105c5f
--- /dev/null
+++ b/src/opencode/types/vc_diff_params.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Required, TypeAlias, TypedDict
+
+from .shared.vcs_mode import VcsMode
+
+__all__ = ["VcDiffParams", "Location"]
+
+
+class VcDiffParams(TypedDict, total=False):
+ mode: Required[VcsMode]
+
+ base: Optional[str]
+
+ context: Optional[int]
+
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/vc_diff_response.py b/src/opencode/types/vc_diff_response.py
new file mode 100644
index 0000000..360e174
--- /dev/null
+++ b/src/opencode/types/vc_diff_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.file_diff_info import FileDiffInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["VcDiffResponse"]
+
+
+class VcDiffResponse(BaseModel):
+ data: List[FileDiffInfo]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/vc_get_params.py b/src/opencode/types/vc_get_params.py
new file mode 100644
index 0000000..e4d7248
--- /dev/null
+++ b/src/opencode/types/vc_get_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["VcGetParams", "Location"]
+
+
+class VcGetParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/vc_get_response.py b/src/opencode/types/vc_get_response.py
new file mode 100644
index 0000000..5d2a648
--- /dev/null
+++ b/src/opencode/types/vc_get_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.vcs_info import VcsInfo
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["VcGetResponse"]
+
+
+class VcGetResponse(BaseModel):
+ data: VcsInfo
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/vc_init_params.py b/src/opencode/types/vc_init_params.py
new file mode 100644
index 0000000..b41dbdc
--- /dev/null
+++ b/src/opencode/types/vc_init_params.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["VcInitParams", "Location"]
+
+
+class VcInitParams(TypedDict, total=False):
+ location: Optional[Location]
+
+ provider: Optional[str]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/vc_status_params.py b/src/opencode/types/vc_status_params.py
new file mode 100644
index 0000000..fa67ea9
--- /dev/null
+++ b/src/opencode/types/vc_status_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["VcStatusParams", "Location"]
+
+
+class VcStatusParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/vc_status_response.py b/src/opencode/types/vc_status_response.py
new file mode 100644
index 0000000..682d771
--- /dev/null
+++ b/src/opencode/types/vc_status_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.vcs_file_status import VcsFileStatus
+from .shared.location_public_ref import LocationPublicRef
+
+__all__ = ["VcStatusResponse"]
+
+
+class VcStatusResponse(BaseModel):
+ data: List[VcsFileStatus]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/vcs/__init__.py b/src/opencode/types/vcs/__init__.py
new file mode 100644
index 0000000..d5120f3
--- /dev/null
+++ b/src/opencode/types/vcs/__init__.py
@@ -0,0 +1,6 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .branch_list_params import BranchListParams as BranchListParams
+from .branch_list_response import BranchListResponse as BranchListResponse
diff --git a/src/opencode/types/vcs/branch_list_params.py b/src/opencode/types/vcs/branch_list_params.py
new file mode 100644
index 0000000..ff73bb4
--- /dev/null
+++ b/src/opencode/types/vcs/branch_list_params.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["BranchListParams", "Location"]
+
+
+class BranchListParams(TypedDict, total=False):
+ limit: Optional[int]
+
+ location: Optional[Location]
+
+ search: Optional[str]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/vcs/branch_list_response.py b/src/opencode/types/vcs/branch_list_response.py
new file mode 100644
index 0000000..c22cac3
--- /dev/null
+++ b/src/opencode/types/vcs/branch_list_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from ..shared.vcs_branch_list import VcsBranchList
+from ..shared.location_public_ref import LocationPublicRef
+
+__all__ = ["BranchListResponse"]
+
+
+class BranchListResponse(BaseModel):
+ data: VcsBranchList
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/voice_session_params.py b/src/opencode/types/voice_session_params.py
new file mode 100644
index 0000000..2478f88
--- /dev/null
+++ b/src/opencode/types/voice_session_params.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Optional
+from typing_extensions import Required, TypedDict
+
+from .shared.voice_transport_input import VoiceTransportInput
+
+__all__ = ["VoiceSessionParams"]
+
+
+class VoiceSessionParams(TypedDict, total=False):
+ model: Required[str]
+
+ transport: Required[VoiceTransportInput]
+
+ session: Optional[Dict[str, object]]
diff --git a/src/opencode/types/websearch_providers_params.py b/src/opencode/types/websearch_providers_params.py
new file mode 100644
index 0000000..6b1c209
--- /dev/null
+++ b/src/opencode/types/websearch_providers_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import TypeAlias, TypedDict
+
+__all__ = ["WebsearchProvidersParams", "Location"]
+
+
+class WebsearchProvidersParams(TypedDict, total=False):
+ location: Optional[Location]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/websearch_providers_response.py b/src/opencode/types/websearch_providers_response.py
new file mode 100644
index 0000000..6006e28
--- /dev/null
+++ b/src/opencode/types/websearch_providers_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict, List
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.location_public_ref import LocationPublicRef
+from .shared.web_search_provider import WebSearchProvider
+
+__all__ = ["WebsearchProvidersResponse"]
+
+
+class WebsearchProvidersResponse(BaseModel):
+ data: List[WebSearchProvider]
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/websearch_query_params.py b/src/opencode/types/websearch_query_params.py
new file mode 100644
index 0000000..272dc51
--- /dev/null
+++ b/src/opencode/types/websearch_query_params.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union, Optional
+from typing_extensions import Required, Annotated, TypeAlias, TypedDict
+
+from .._utils import PropertyInfo
+from .shared.web_search_id import WebSearchID
+
+__all__ = ["WebsearchQueryParams", "Location"]
+
+
+class WebsearchQueryParams(TypedDict, total=False):
+ query: Required[str]
+
+ location: Optional[Location]
+
+ provider_id: Annotated[WebSearchID, PropertyInfo(alias="providerID")]
+
+
+class LocationTyped(TypedDict, total=False):
+ directory: Optional[str]
+
+
+Location: TypeAlias = Union[LocationTyped, Dict[str, object]]
diff --git a/src/opencode/types/websearch_query_response.py b/src/opencode/types/websearch_query_response.py
new file mode 100644
index 0000000..0414c5f
--- /dev/null
+++ b/src/opencode/types/websearch_query_response.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import TYPE_CHECKING, Dict
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.location_public_ref import LocationPublicRef
+from .shared.web_search_response import WebSearchResponse
+
+__all__ = ["WebsearchQueryResponse"]
+
+
+class WebsearchQueryResponse(BaseModel):
+ data: WebSearchResponse
+
+ location: LocationPublicRef
+
+ if TYPE_CHECKING:
+ # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
+ # value to this field, so for compatibility we avoid doing it at runtime.
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ # Stub to indicate that arbitrary properties are accepted.
+ # To access properties that are not valid identifiers you can use `getattr`, e.g.
+ # `getattr(obj, '$type')`
+ def __getattr__(self, attr: str) -> object: ...
+ else:
+ __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/worktree_create_params.py b/src/opencode/types/worktree_create_params.py
new file mode 100644
index 0000000..bc49eee
--- /dev/null
+++ b/src/opencode/types/worktree_create_params.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .shared.project_id import ProjectID
+from .shared.absolute_path import AbsolutePath
+
+__all__ = ["WorktreeCreateParams"]
+
+
+class WorktreeCreateParams(TypedDict, total=False):
+ project_id: Required[Annotated[ProjectID, PropertyInfo(alias="projectID")]]
+
+ branch: str
+
+ directory: AbsolutePath
+
+ from_: Annotated[AbsolutePath, PropertyInfo(alias="from")]
+
+ name: str
diff --git a/src/opencode/types/worktree_list_params.py b/src/opencode/types/worktree_list_params.py
new file mode 100644
index 0000000..1c991b8
--- /dev/null
+++ b/src/opencode/types/worktree_list_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .shared.project_id import ProjectID
+
+__all__ = ["WorktreeListParams"]
+
+
+class WorktreeListParams(TypedDict, total=False):
+ project_id: Required[Annotated[ProjectID, PropertyInfo(alias="projectID")]]
diff --git a/src/opencode/types/worktree_refresh_params.py b/src/opencode/types/worktree_refresh_params.py
new file mode 100644
index 0000000..5e74a2a
--- /dev/null
+++ b/src/opencode/types/worktree_refresh_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .shared.project_id import ProjectID
+
+__all__ = ["WorktreeRefreshParams"]
+
+
+class WorktreeRefreshParams(TypedDict, total=False):
+ project_id: Required[Annotated[ProjectID, PropertyInfo(alias="projectID")]]
diff --git a/src/opencode/types/worktree_remove_params.py b/src/opencode/types/worktree_remove_params.py
new file mode 100644
index 0000000..743fcc4
--- /dev/null
+++ b/src/opencode/types/worktree_remove_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+from .shared.project_id import ProjectID
+from .shared.absolute_path import AbsolutePath
+
+__all__ = ["WorktreeRemoveParams"]
+
+
+class WorktreeRemoveParams(TypedDict, total=False):
+ directory: Required[AbsolutePath]
+
+ force: Required[bool]
+
+ project_id: Required[Annotated[ProjectID, PropertyInfo(alias="projectID")]]
diff --git a/stainless.yml b/stainless.yml
new file mode 100644
index 0000000..7b56db0
--- /dev/null
+++ b/stainless.yml
@@ -0,0 +1,791 @@
+# stainless.yml for shrijayan/opencode-python-sdk v0.2.3, written as an adopter would.
+# The resource tree is drafted from the SDK's api.md by scripts/opencode/api_md_to_config.py;
+# client settings follow src/opencode/_client.py. The spec comes from
+# scripts/opencode/contract_to_openapi.py (ADR 0015); the models and casings between the
+# ferrule: markers from scripts/opencode/config_models.py, which keeps the SDK's type names.
+organization:
+ name: opencode
+ docs: https://opencode.ai/docs
+ contact: https://github.com/shrijayan/opencode-python-sdk/issues
+
+security:
+ - basicAuth: []
+
+security_schemes:
+ basicAuth:
+ type: http
+ scheme: basic
+
+client_settings:
+ default_client_name: Opencode
+ default_env_prefix: OPENCODE
+ opts:
+ username:
+ type: string
+ nullable: true
+ default: opencode
+ auth:
+ security_scheme: basicAuth
+ role: username
+ password:
+ type: string
+ nullable: true
+ read_env: OPENCODE_PASSWORD
+ auth:
+ security_scheme: basicAuth
+ role: password
+
+environments:
+ production: http://127.0.0.1:4096
+
+query_settings:
+ # The server reads arrays as repeated keys and objects as `location[directory]=` (deepObject).
+ array_format: repeat
+ nested_format: brackets
+
+pagination:
+ # `{data: [...], cursor: {next, previous}}`: message.list and session.list.
+ - name: cursor_page
+ type: cursor
+ request:
+ cursor:
+ type: string
+ x-stainless-pagination-property:
+ purpose: next_cursor_param
+ response:
+ data:
+ type: array
+ items: {}
+ x-stainless-pagination-property:
+ purpose: items
+ cursor:
+ type: object
+ properties:
+ next:
+ type: string
+ x-stainless-pagination-property:
+ purpose: next_cursor_field
+
+targets:
+ python:
+ package_name: opencode
+ project_name: opencode-python-sdk
+ publish:
+ pypi: false
+ # The SDK keeps its own project files and tooling (MIT licence, uv, hatch-vcs, its release
+ # workflow); these generated files are never written over or added.
+ keep_files:
+ - .devcontainer/Dockerfile
+ - .devcontainer/devcontainer.json
+ - .gitignore
+ - .python-version
+ - .release-please-manifest.json
+ - .stats.yml
+ - .vscode/settings.json
+ - Brewfile
+ - CONTRIBUTING.md
+ - LICENSE
+ - README.md
+ - SECURITY.md
+ - bin/check-release-environment
+ - bin/publish-pypi
+ - examples/.keep
+ - noxfile.py
+ - pyproject.toml
+ - release-please-config.json
+ - requirements-dev.lock
+ - requirements.lock
+ - scripts/bootstrap
+ - scripts/format
+ - scripts/lint
+ - scripts/mock
+ - scripts/test
+ # The version comes from the git tag (hatch-vcs), not from release-please.
+ - src/opencode/_version.py
+
+# written by scripts/opencode/config_models.py
+custom_casings:
+ id1: {initialism: true}
+ lsp: {initialism: true}
+ rtc: {initialism: true}
+ usd: {initialism: true}
+#
+readme:
+ example_requests:
+ default:
+ type: request
+ endpoint: get /api/session
+ params: {}
+ headline:
+ type: request
+ endpoint: get /api/session
+ params: {}
+
+resources:
+ # written by scripts/opencode/config_models.py
+ $shared:
+ models:
+ absolute_path: "#/components/schemas/AbsolutePath"
+ agent_color: "#/components/schemas/Agent.Color"
+ agent_id: "#/components/schemas/Agent.ID"
+ agent_info: "#/components/schemas/Agent.Info"
+ agent_not_found_error_body: "#/components/schemas/AgentNotFoundErrorEncoded"
+ agent_updated_event: "#/components/schemas/agent.updated"
+ command_execution_error_body: "#/components/schemas/CommandExecutionErrorEncoded"
+ command_info: "#/components/schemas/Command.Info"
+ command_not_found_error_body: "#/components/schemas/CommandNotFoundErrorEncoded"
+ command_updated_event: "#/components/schemas/command.updated"
+ config_agent: "#/components/schemas/Config.AgentEncoded"
+ config_command: "#/components/schemas/Config.CommandEncoded"
+ config_directory: "#/components/schemas/Config.DirectoryEncoded"
+ config_document: "#/components/schemas/Config.DocumentEncoded"
+ config_entry: "#/components/schemas/Config.Entry"
+ config_formatter_entry: "#/components/schemas/Config.Formatter.EntryEncoded"
+ config_info: "#/components/schemas/Config.InfoEncoded"
+ config_lsp_server: "#/components/schemas/Config.LSP.ServerEncoded"
+ config_model: "#/components/schemas/Config.ModelEncoded"
+ config_model_capabilities: "#/components/schemas/Config.Model.Capabilities"
+ config_model_cost: "#/components/schemas/Config.Model.CostEncoded"
+ config_model_reasoning: "#/components/schemas/Config.Model.Reasoning"
+ config_model_settings: "#/components/schemas/Config.Model.Settings"
+ config_plugin_entry: "#/components/schemas/Config.Plugin.EntryEncoded"
+ config_provider: "#/components/schemas/Config.ProviderEncoded"
+ config_provider_settings: "#/components/schemas/Config.Provider.Settings"
+ config_reference_git: "#/components/schemas/Config.Reference.GitEncoded"
+ config_reference_local: "#/components/schemas/Config.Reference.LocalEncoded"
+ config_shell_option: "#/components/schemas/ConfigShell.Option"
+ config_updated_event: "#/components/schemas/config.updated"
+ config_warming: "#/components/schemas/Config.WarmingEncoded"
+ config_web_search_info: "#/components/schemas/ConfigWebSearch.InfoEncoded"
+ config_worktree: "#/components/schemas/Config.Worktree"
+ conflict_error_body: "#/components/schemas/ConflictErrorEncoded"
+ connection_credential_info: "#/components/schemas/Connection.CredentialInfo"
+ connection_env_info: "#/components/schemas/Connection.EnvInfo"
+ connection_info: "#/components/schemas/Connection.Info"
+ connection_status: "#/components/schemas/Connection.Status"
+ credential_entry: "#/components/schemas/Credential.Entry"
+ credential_external: "#/components/schemas/Credential.External"
+ credential_id: "#/components/schemas/Credential.ID"
+ credential_key: "#/components/schemas/Credential.Key"
+ credential_o_auth: "#/components/schemas/Credential.OAuth"
+ credential_switched_event: "#/components/schemas/credential.switched"
+ credential_updated_event: "#/components/schemas/credential.updated"
+ credential_value: "#/components/schemas/Credential.Value"
+ event: "#/components/schemas/V2Event"
+ event_id: "#/components/schemas/Event.ID"
+ event_log_synced: "#/components/schemas/EventLog.Synced"
+ event_seq: "#/components/schemas/Event.Seq"
+ event_version: "#/components/schemas/Event.VersionEncoded"
+ event_version10: "#/components/schemas/Event.VersionEncoded_10"
+ file_diff_info: "#/components/schemas/FileDiff.Info"
+ file_not_found_error_body: "#/components/schemas/FileNotFoundErrorEncoded"
+ file_system_entry: "#/components/schemas/FileSystem.Entry"
+ file_system_write: "#/components/schemas/FileSystem.Write"
+ filesystem_changed_event: "#/components/schemas/filesystem.changed"
+ forbidden_error_body: "#/components/schemas/ForbiddenErrorEncoded"
+ form_already_settled_error_body: "#/components/schemas/FormAlreadySettledErrorEncoded"
+ form_answer: "#/components/schemas/Form.Answer"
+ form_boolean_field: "#/components/schemas/Form.BooleanField"
+ form_cancelled_event: "#/components/schemas/form.cancelled"
+ form_created_event: "#/components/schemas/form.created"
+ form_detail: "#/components/schemas/Form.Detail"
+ form_external_field: "#/components/schemas/Form.ExternalField"
+ form_field: "#/components/schemas/Form.Field"
+ form_fields: "#/components/schemas/Form.Fields"
+ form_fields1: "#/components/schemas/Form.Fields_1"
+ form_fields2: "#/components/schemas/Form.Fields_2"
+ form_fields3: "#/components/schemas/Form.Fields_3"
+ form_id: "#/components/schemas/Form.ID"
+ form_info: "#/components/schemas/Form.Info"
+ form_integer_field: "#/components/schemas/Form.IntegerField"
+ form_invalid_answer_error_body: "#/components/schemas/FormInvalidAnswerErrorEncoded"
+ form_metadata: "#/components/schemas/Form.Metadata"
+ form_multiselect_field: "#/components/schemas/Form.MultiselectField"
+ form_not_found_error_body: "#/components/schemas/FormNotFoundErrorEncoded"
+ form_number_field: "#/components/schemas/Form.NumberField"
+ form_option: "#/components/schemas/Form.Option"
+ form_replied_event: "#/components/schemas/form.replied"
+ form_state: "#/components/schemas/Form.State"
+ form_string_field: "#/components/schemas/Form.StringField"
+ form_value: "#/components/schemas/Form.Value"
+ form_when: "#/components/schemas/Form.When"
+ generate_text_response: "#/components/schemas/GenerateTextResponse"
+ installation_update_available_event: "#/components/schemas/installation.update-available"
+ installation_updated_event: "#/components/schemas/installation.updated"
+ instruction_entry_info: "#/components/schemas/InstructionEntry.Info"
+ instruction_entry_key: "#/components/schemas/InstructionEntry.Key"
+ instruction_entry_snapshot: "#/components/schemas/InstructionEntry.Snapshot"
+ instruction_entry_value_too_large_error_body: "#/components/schemas/InstructionEntryValueTooLargeErrorEncoded"
+ instruction_hash: "#/components/schemas/Instruction.Hash"
+ instruction_key: "#/components/schemas/Instruction.Key"
+ integration_attempt: "#/components/schemas/Integration.AttemptEncoded"
+ integration_attempt_id: "#/components/schemas/Integration.AttemptID"
+ integration_attempt_not_found_error_body: "#/components/schemas/IntegrationAttemptNotFoundErrorEncoded"
+ integration_attempt_status: "#/components/schemas/Integration.AttemptStatus"
+ integration_command_attempt: "#/components/schemas/Integration.CommandAttempt"
+ integration_command_attempt_status: "#/components/schemas/Integration.CommandAttemptStatus"
+ integration_command_method: "#/components/schemas/Integration.CommandMethod"
+ integration_env_method: "#/components/schemas/Integration.EnvMethod"
+ integration_external_method: "#/components/schemas/Integration.ExternalMethod"
+ integration_id: "#/components/schemas/Integration.ID"
+ integration_info: "#/components/schemas/Integration.Info"
+ integration_key_method: "#/components/schemas/Integration.KeyMethod"
+ integration_method: "#/components/schemas/Integration.Method"
+ integration_method_id: "#/components/schemas/Integration.MethodID"
+ integration_method_not_found_error_body: "#/components/schemas/IntegrationMethodNotFoundErrorEncoded"
+ integration_not_found_error_body: "#/components/schemas/IntegrationNotFoundErrorEncoded"
+ integration_o_auth_method: "#/components/schemas/Integration.OAuthMethod"
+ integration_updated_event: "#/components/schemas/integration.updated"
+ invalid_cursor_error_body: "#/components/schemas/InvalidCursorErrorEncoded"
+ invalid_request_error_body: "#/components/schemas/InvalidRequestErrorEncoded"
+ location_not_found_error_body: "#/components/schemas/LocationNotFoundErrorEncoded"
+ location_public_info: "#/components/schemas/Location.PublicInfo"
+ location_public_ref: "#/components/schemas/Location.PublicRef"
+ location_ref: "#/components/schemas/Location.Ref"
+ location_shutdown_event: "#/components/schemas/location.shutdown"
+ mcp_local_config: "#/components/schemas/Mcp.LocalConfigEncoded"
+ mcp_o_auth_config: "#/components/schemas/Mcp.OAuthConfigEncoded"
+ mcp_protocol: "#/components/schemas/Mcp.Protocol"
+ mcp_remote_config: "#/components/schemas/Mcp.RemoteConfigEncoded"
+ mcp_resource: "#/components/schemas/Mcp.Resource"
+ mcp_resource_catalog: "#/components/schemas/Mcp.ResourceCatalog"
+ mcp_resource_template: "#/components/schemas/Mcp.ResourceTemplate"
+ mcp_resources_changed_event: "#/components/schemas/mcp.resources.changed"
+ mcp_server: "#/components/schemas/Mcp.Server"
+ mcp_server_not_found_error_body: "#/components/schemas/McpServerNotFoundErrorEncoded"
+ mcp_status_changed_event: "#/components/schemas/mcp.status.changed"
+ mcp_status_connected: "#/components/schemas/Mcp.Status.Connected"
+ mcp_status_disabled: "#/components/schemas/Mcp.Status.Disabled"
+ mcp_status_failed: "#/components/schemas/Mcp.Status.Failed"
+ mcp_status_needs_auth: "#/components/schemas/Mcp.Status.NeedsAuth"
+ mcp_status_pending: "#/components/schemas/Mcp.Status.Pending"
+ message_not_found_error_body: "#/components/schemas/MessageNotFoundErrorEncoded"
+ model_cache_format: "#/components/schemas/Model.CacheFormat"
+ model_capabilities: "#/components/schemas/Model.Capabilities"
+ model_compatibility: "#/components/schemas/Model.Compatibility"
+ model_cost: "#/components/schemas/Model.Cost"
+ model_info: "#/components/schemas/Model.Info"
+ model_max_tokens_field: "#/components/schemas/Model.MaxTokensField"
+ model_reasoning_field: "#/components/schemas/Model.ReasoningField"
+ model_ref: "#/components/schemas/Model.Ref"
+ model_settings: "#/components/schemas/Model.Settings"
+ model_updated_event: "#/components/schemas/model.updated"
+ model_variant: "#/components/schemas/Model.Variant"
+ models_dev_refreshed_event: "#/components/schemas/models-dev.refreshed"
+ money_usd: "#/components/schemas/Money.USD"
+ money_usd_per_million_tokens: "#/components/schemas/Money.USDPerMillionTokens"
+ pairing_code: "#/components/schemas/PairingCode"
+ pairing_session: "#/components/schemas/PairingSession"
+ permission_asked_event: "#/components/schemas/permission.asked"
+ permission_effect: "#/components/schemas/Permission.Effect"
+ permission_id: "#/components/schemas/Permission.ID"
+ permission_not_found_error_body: "#/components/schemas/PermissionNotFoundErrorEncoded"
+ permission_replied_event: "#/components/schemas/permission.replied"
+ permission_reply: "#/components/schemas/Permission.Reply"
+ permission_request: "#/components/schemas/Permission.Request"
+ permission_rule: "#/components/schemas/Permission.Rule"
+ permission_ruleset: "#/components/schemas/Permission.Ruleset"
+ permission_saved_id: "#/components/schemas/PermissionSaved.ID"
+ permission_saved_info: "#/components/schemas/PermissionSaved.Info"
+ permission_saved_not_found_error_body: "#/components/schemas/PermissionSavedNotFoundErrorEncoded"
+ permission_source: "#/components/schemas/Permission.Source"
+ persistent_pty_added_event: "#/components/schemas/persistent-pty.added"
+ persistent_pty_handoff: "#/components/schemas/PersistentPty.Handoff"
+ persistent_pty_info: "#/components/schemas/PersistentPty.Info"
+ persistent_pty_read_lines: "#/components/schemas/PersistentPty.ReadLines"
+ persistent_pty_read_result: "#/components/schemas/PersistentPty.ReadResult"
+ persistent_pty_removed_event: "#/components/schemas/persistent-pty.removed"
+ persistent_pty_snapshot: "#/components/schemas/PersistentPty.Snapshot"
+ plugin_features: "#/components/schemas/Plugin.Features"
+ plugin_info: "#/components/schemas/Plugin.Info"
+ plugin_source: "#/components/schemas/Plugin.Source"
+ plugin_state: "#/components/schemas/Plugin.State"
+ plugin_updated_event: "#/components/schemas/plugin.updated"
+ project: "#/components/schemas/Project"
+ project_commands: "#/components/schemas/Project.Commands"
+ project_icon: "#/components/schemas/Project.Icon"
+ project_id: "#/components/schemas/Project.ID"
+ project_not_found_error_body: "#/components/schemas/ProjectNotFoundErrorEncoded"
+ project_time: "#/components/schemas/Project.Time"
+ project_updated_event: "#/components/schemas/project.updated"
+ project_vcs: "#/components/schemas/Project.Vcs"
+ prompt_agent_attachment: "#/components/schemas/Prompt.AgentAttachment"
+ prompt_base64: "#/components/schemas/Prompt.Base64"
+ prompt_file_attachment: "#/components/schemas/Prompt.FileAttachment"
+ prompt_file_source: "#/components/schemas/Prompt.FileSource"
+ prompt_input_file_attachment: "#/components/schemas/PromptInput.FileAttachment"
+ prompt_input_skill_attachment: "#/components/schemas/PromptInput.SkillAttachment"
+ prompt_mention: "#/components/schemas/Prompt.Mention"
+ prompt_skill_attachment: "#/components/schemas/Prompt.SkillAttachment"
+ provider_compaction: "#/components/schemas/Provider.Compaction"
+ provider_compatibility: "#/components/schemas/Provider.Compatibility"
+ provider_encoding: "#/components/schemas/Provider.Encoding"
+ provider_id: "#/components/schemas/Provider.ID"
+ provider_info: "#/components/schemas/Provider.Info"
+ provider_not_found_error_body: "#/components/schemas/ProviderNotFoundErrorEncoded"
+ provider_request: "#/components/schemas/Provider.Request"
+ provider_settings: "#/components/schemas/Provider.Settings"
+ provider_transport: "#/components/schemas/Provider.Transport"
+ provider_updated_event: "#/components/schemas/provider.updated"
+ pty: "#/components/schemas/Pty"
+ pty_created_event: "#/components/schemas/pty.created"
+ pty_deleted_event: "#/components/schemas/pty.deleted"
+ pty_exited_event: "#/components/schemas/pty.exited"
+ pty_id: "#/components/schemas/Pty.ID"
+ pty_not_found_error_body: "#/components/schemas/PtyNotFoundErrorEncoded"
+ pty_ticket_connect_token: "#/components/schemas/PtyTicket.ConnectToken"
+ pty_updated_event: "#/components/schemas/pty.updated"
+ reference_git_source: "#/components/schemas/Reference.GitSource"
+ reference_info: "#/components/schemas/Reference.Info"
+ reference_local_source: "#/components/schemas/Reference.LocalSource"
+ reference_source: "#/components/schemas/Reference.Source"
+ reference_updated_event: "#/components/schemas/reference.updated"
+ relative_path: "#/components/schemas/RelativePath"
+ rpc_error_body: "#/components/schemas/RpcErrorEncoded"
+ rpc_event: "#/components/schemas/V2Event.rpc"
+ rpc_internal_error_body: "#/components/schemas/RpcInternalErrorEncoded"
+ rpc_output: "#/components/schemas/Rpc.Output"
+ server_connected_event: "#/components/schemas/V2Event.server.connected"
+ server_info: "#/components/schemas/ServerInfo"
+ service_unavailable_error_body: "#/components/schemas/ServiceUnavailableErrorEncoded"
+ session_active: "#/components/schemas/SessionActive"
+ session_agent_selected_event: "#/components/schemas/session.agent.selected"
+ session_archived_event: "#/components/schemas/session.archived"
+ session_busy_error_body: "#/components/schemas/SessionBusyErrorEncoded"
+ session_compaction_delta_event: "#/components/schemas/session.compaction.delta"
+ session_compaction_ended_event: "#/components/schemas/session.compaction.ended"
+ session_compaction_failed_event: "#/components/schemas/session.compaction.failed"
+ session_compaction_started_event: "#/components/schemas/session.compaction.started"
+ session_created_event: "#/components/schemas/session.created"
+ session_deleted_event: "#/components/schemas/session.deleted"
+ session_event_durable: "#/components/schemas/Session.Event.Durable"
+ session_execution_failed_event: "#/components/schemas/session.execution.failed"
+ session_execution_interrupted_event: "#/components/schemas/session.execution.interrupted"
+ session_execution_started_event: "#/components/schemas/session.execution.started"
+ session_execution_succeeded_event: "#/components/schemas/session.execution.succeeded"
+ session_fork_boundary: "#/components/schemas/Session.ForkBoundary"
+ session_forked_event: "#/components/schemas/session.forked"
+ session_generate_response: "#/components/schemas/SessionGenerateResponse"
+ session_id: "#/components/schemas/Session.ID"
+ session_id1: "#/components/schemas/Session.ID_1"
+ session_idle_event: "#/components/schemas/session.idle"
+ session_inbox_cancelled_event: "#/components/schemas/session.inbox.cancelled"
+ session_inbox_compaction: "#/components/schemas/Session.Inbox.Compaction"
+ session_inbox_compaction_payload: "#/components/schemas/Session.Inbox.CompactionPayload"
+ session_inbox_delivered_event: "#/components/schemas/session.inbox.delivered"
+ session_inbox_delivery: "#/components/schemas/Session.Inbox.Delivery"
+ session_inbox_delivery_changed_event: "#/components/schemas/session.inbox.delivery.changed"
+ session_inbox_enqueued_event: "#/components/schemas/session.inbox.enqueued"
+ session_inbox_info: "#/components/schemas/Session.Inbox.Info"
+ session_inbox_item: "#/components/schemas/Session.Inbox.Item"
+ session_inbox_move: "#/components/schemas/Session.Inbox.Move"
+ session_inbox_move_payload: "#/components/schemas/Session.Inbox.MovePayload"
+ session_inbox_move_payload1: "#/components/schemas/Session.Inbox.MovePayload_1"
+ session_inbox_synthetic: "#/components/schemas/Session.Inbox.Synthetic"
+ session_inbox_synthetic_payload: "#/components/schemas/Session.Inbox.SyntheticPayload"
+ session_inbox_user: "#/components/schemas/Session.Inbox.User"
+ session_inbox_user_payload: "#/components/schemas/Session.Inbox.UserPayload"
+ session_info: "#/components/schemas/Session.Info"
+ session_instructions_updated_event: "#/components/schemas/session.instructions.updated"
+ session_interrupt_response: "#/components/schemas/SessionInterruptResponse"
+ session_log_item: "#/components/schemas/SessionLogItem"
+ session_message_agent_selected: "#/components/schemas/Session.Message.AgentSelected"
+ session_message_assistant: "#/components/schemas/Session.Message.Assistant"
+ session_message_assistant_content: "#/components/schemas/Session.Message.AssistantContent.Encoded"
+ session_message_assistant_reasoning: "#/components/schemas/Session.Message.Assistant.Reasoning"
+ session_message_assistant_reasoning1: "#/components/schemas/Session.Message.Assistant.Reasoning_1"
+ session_message_assistant_retry: "#/components/schemas/Session.Message.Assistant.Retry"
+ session_message_assistant_text: "#/components/schemas/Session.Message.Assistant.Text"
+ session_message_assistant_text1: "#/components/schemas/Session.Message.Assistant.Text_1"
+ session_message_assistant_tool: "#/components/schemas/Session.Message.Assistant.Tool"
+ session_message_assistant_tool1: "#/components/schemas/Session.Message.Assistant.Tool_1"
+ session_message_compaction: "#/components/schemas/Session.Message.Compaction"
+ session_message_compaction_completed: "#/components/schemas/Session.Message.Compaction.Completed"
+ session_message_compaction_failed: "#/components/schemas/Session.Message.Compaction.Failed"
+ session_message_compaction_running: "#/components/schemas/Session.Message.Compaction.Running"
+ session_message_content_updated_event: "#/components/schemas/session.message.content.updated"
+ session_message_id: "#/components/schemas/Session.Message.ID"
+ session_message_idle: "#/components/schemas/Session.Message.Idle"
+ session_message_info: "#/components/schemas/Session.Message.Info"
+ session_message_location_switched: "#/components/schemas/Session.Message.LocationSwitched"
+ session_message_model_selected: "#/components/schemas/Session.Message.ModelSelected"
+ session_message_native: "#/components/schemas/Session.Message.Native"
+ session_message_native1: "#/components/schemas/Session.Message.Native_1"
+ session_message_native10: "#/components/schemas/Session.Message.Native_10"
+ session_message_native11: "#/components/schemas/Session.Message.Native_11"
+ session_message_native12: "#/components/schemas/Session.Message.Native_12"
+ session_message_native13: "#/components/schemas/Session.Message.Native_13"
+ session_message_native14: "#/components/schemas/Session.Message.Native_14"
+ session_message_native15: "#/components/schemas/Session.Message.Native_15"
+ session_message_native2: "#/components/schemas/Session.Message.Native_2"
+ session_message_native3: "#/components/schemas/Session.Message.Native_3"
+ session_message_native4: "#/components/schemas/Session.Message.Native_4"
+ session_message_native5: "#/components/schemas/Session.Message.Native_5"
+ session_message_native6: "#/components/schemas/Session.Message.Native_6"
+ session_message_native7: "#/components/schemas/Session.Message.Native_7"
+ session_message_native8: "#/components/schemas/Session.Message.Native_8"
+ session_message_native9: "#/components/schemas/Session.Message.Native_9"
+ session_message_shell: "#/components/schemas/Session.Message.Shell"
+ session_message_skill: "#/components/schemas/Session.Message.Skill"
+ session_message_synthetic: "#/components/schemas/Session.Message.Synthetic"
+ session_message_system: "#/components/schemas/Session.Message.System"
+ session_message_tool_state_completed: "#/components/schemas/Session.Message.ToolState.Completed"
+ session_message_tool_state_completed1: "#/components/schemas/Session.Message.ToolState.Completed_1"
+ session_message_tool_state_error: "#/components/schemas/Session.Message.ToolState.Error"
+ session_message_tool_state_error1: "#/components/schemas/Session.Message.ToolState.Error_1"
+ session_message_tool_state_running: "#/components/schemas/Session.Message.ToolState.Running"
+ session_message_tool_state_running1: "#/components/schemas/Session.Message.ToolState.Running_1"
+ session_message_tool_state_streaming: "#/components/schemas/Session.Message.ToolState.Streaming"
+ session_message_user: "#/components/schemas/Session.Message.User"
+ session_messages_response: "#/components/schemas/SessionMessagesResponse"
+ session_metadata: "#/components/schemas/Session.Metadata"
+ session_metadata_updated_event: "#/components/schemas/session.metadata.updated"
+ session_model_selected_event: "#/components/schemas/session.model.selected"
+ session_moved_event: "#/components/schemas/session.moved"
+ session_not_found_error_body: "#/components/schemas/SessionNotFoundErrorEncoded"
+ session_permissions_event: "#/components/schemas/session.permissions"
+ session_reasoning_delta_event: "#/components/schemas/session.reasoning.delta"
+ session_reasoning_ended_event: "#/components/schemas/session.reasoning.ended"
+ session_reasoning_started_event: "#/components/schemas/session.reasoning.started"
+ session_renamed_event: "#/components/schemas/session.renamed"
+ session_retry_scheduled_event: "#/components/schemas/session.retry.scheduled"
+ session_revert: "#/components/schemas/Session.Revert"
+ session_revert_cleared_event: "#/components/schemas/session.revert.cleared"
+ session_revert_committed_event: "#/components/schemas/session.revert.committed"
+ session_revert_staged_event: "#/components/schemas/session.revert.staged"
+ session_shell_ended_event: "#/components/schemas/session.shell.ended"
+ session_shell_started_event: "#/components/schemas/session.shell.started"
+ session_skill_activated_event: "#/components/schemas/session.skill.activated"
+ session_stats_activity: "#/components/schemas/SessionStats.Activity"
+ session_stats_info: "#/components/schemas/SessionStats.Info"
+ session_stats_model_usage: "#/components/schemas/SessionStats.ModelUsage"
+ session_stats_tool_totals: "#/components/schemas/SessionStats.ToolTotals"
+ session_stats_tool_usage: "#/components/schemas/SessionStats.ToolUsage"
+ session_stats_tools: "#/components/schemas/SessionStats.Tools"
+ session_status: "#/components/schemas/SessionStatus"
+ session_status_updated: "#/components/schemas/SessionStatusUpdated"
+ session_step_ended_event: "#/components/schemas/session.step.ended"
+ session_step_failed_event: "#/components/schemas/session.step.failed"
+ session_step_started_event: "#/components/schemas/session.step.started"
+ session_step_streamed_event: "#/components/schemas/session.step.streamed"
+ session_structured_error: "#/components/schemas/Session.StructuredError"
+ session_structured_error1: "#/components/schemas/Session.StructuredError_1"
+ session_synthetic_event: "#/components/schemas/session.synthetic"
+ session_text_delta_event: "#/components/schemas/session.text.delta"
+ session_text_ended_event: "#/components/schemas/session.text.ended"
+ session_text_started_event: "#/components/schemas/session.text.started"
+ session_tool_called_event: "#/components/schemas/session.tool.called"
+ session_tool_failed_event: "#/components/schemas/session.tool.failed"
+ session_tool_input_delta_event: "#/components/schemas/session.tool.input.delta"
+ session_tool_input_ended_event: "#/components/schemas/session.tool.input.ended"
+ session_tool_input_started_event: "#/components/schemas/session.tool.input.started"
+ session_tool_progress_event: "#/components/schemas/session.tool.progress"
+ session_tool_success_event: "#/components/schemas/session.tool.success"
+ session_transfer_data: "#/components/schemas/SessionTransfer.Data"
+ session_unarchived_event: "#/components/schemas/session.unarchived"
+ session_usage_recorded_event: "#/components/schemas/session.usage.recorded"
+ session_usage_updated_event: "#/components/schemas/session.usage.updated"
+ session_viewed_event: "#/components/schemas/session.viewed"
+ sessions_cursor: "#/components/schemas/SessionsCursor"
+ sessions_response: "#/components/schemas/SessionsResponse"
+ shell_created_event: "#/components/schemas/shell.created"
+ shell_deleted_event: "#/components/schemas/shell.deleted"
+ shell_exited_event: "#/components/schemas/shell.exited"
+ shell_id: "#/components/schemas/Shell.ID"
+ shell_info: "#/components/schemas/Shell.Info"
+ shell_not_found_error_body: "#/components/schemas/ShellNotFoundErrorEncoded"
+ skill_id: "#/components/schemas/Skill.ID"
+ skill_info: "#/components/schemas/Skill.Info"
+ skill_name: "#/components/schemas/Skill.Name"
+ skill_not_found_error_body: "#/components/schemas/SkillNotFoundErrorEncoded"
+ skill_updated_event: "#/components/schemas/skill.updated"
+ snapshot_id: "#/components/schemas/Snapshot.ID"
+ token_usage_info: "#/components/schemas/TokenUsage.Info"
+ tool_content: "#/components/schemas/Tool.Content"
+ tool_file_content: "#/components/schemas/Tool.FileContent"
+ tool_text_content: "#/components/schemas/Tool.TextContent"
+ tui_command_execute_event: "#/components/schemas/tui.command.execute"
+ tui_prompt_append_event: "#/components/schemas/tui.prompt.append"
+ tui_session_select_event: "#/components/schemas/tui.session.select"
+ tui_toast_show_event: "#/components/schemas/tui.toast.show"
+ unauthorized_error_body: "#/components/schemas/UnauthorizedErrorEncoded"
+ unknown_error_body: "#/components/schemas/UnknownErrorEncoded"
+ vcs_base: "#/components/schemas/Vcs.Base"
+ vcs_branch: "#/components/schemas/Vcs.Branch"
+ vcs_branch_list: "#/components/schemas/Vcs.BranchList"
+ vcs_branch_updated_event: "#/components/schemas/vcs.branch.updated"
+ vcs_file_status: "#/components/schemas/Vcs.FileStatus"
+ vcs_info: "#/components/schemas/Vcs.Info"
+ vcs_init_not_supported_error_body: "#/components/schemas/VcsInitNotSupportedErrorEncoded"
+ vcs_mode: "#/components/schemas/Vcs.Mode"
+ voice_session_response: "#/components/schemas/VoiceSessionResponse"
+ voice_transport: "#/components/schemas/VoiceTransport"
+ voice_transport_input: "#/components/schemas/VoiceTransportInput"
+ voice_web_rtc_transport: "#/components/schemas/VoiceWebRTCTransport"
+ voice_web_rtc_transport_input: "#/components/schemas/VoiceWebRTCTransportInput"
+ voice_web_socket_transport: "#/components/schemas/VoiceWebSocketTransport"
+ voice_web_socket_transport_input: "#/components/schemas/VoiceWebSocketTransportInput"
+ web_search_id: "#/components/schemas/WebSearch.ID"
+ web_search_provider: "#/components/schemas/WebSearch.Provider"
+ web_search_response: "#/components/schemas/WebSearch.ResponseEncoded"
+ web_search_result: "#/components/schemas/WebSearch.Result"
+ websearch_updated_event: "#/components/schemas/websearch.updated"
+ workspace_id: "#/components/schemas/Workspace.ID"
+ worktree_directory: "#/components/schemas/Worktree.Directory"
+ worktree_error_body: "#/components/schemas/WorktreeErrorEncoded"
+ worktree_info: "#/components/schemas/Worktree.Info"
+ worktree_list: "#/components/schemas/Worktree.List"
+ worktree_resolved_event: "#/components/schemas/worktree.resolved"
+ worktree_updated_event: "#/components/schemas/worktree.updated"
+ #
+ agent:
+ methods:
+ list: get /api/agent
+ get: get /api/agent/{agentID}
+ command:
+ methods:
+ list: get /api/command
+ config:
+ methods:
+ get: get /api/config
+ shells: get /api/config/shell
+ update: patch /api/experimental/config
+ credential:
+ methods:
+ list: get /api/credential
+ create: post /api/credential
+ update: patch /api/credential/{credentialID}
+ activate: post /api/credential/{credentialID}/activate
+ remove: delete /api/credential/{credentialID}
+ debug:
+ subresources:
+ location:
+ methods:
+ list: get /api/debug/location
+ evict: delete /api/debug/location
+ event:
+ methods:
+ subscribe:
+ endpoint: get /api/event
+ type: http
+ streaming:
+ param_discriminator: null
+ stream_event_model: $shared.event
+ experimental:
+ subresources:
+ persistent_pty:
+ methods:
+ read: get /api/experimental/session/{sessionID}/terminal/read
+ list: get /api/experimental/session/{sessionID}/terminal
+ create: post /api/experimental/session/{sessionID}/terminal
+ shutdown: post /api/experimental/persistent-pty/shutdown
+ handoff: post /api/experimental/persistent-pty/handoff
+ get: get /api/experimental/persistent-pty/{ptyID}
+ update: put /api/experimental/persistent-pty/{ptyID}
+ snapshot: get /api/experimental/persistent-pty/{ptyID}/snapshot
+ remove: delete /api/experimental/persistent-pty/{ptyID}
+ connect_token: post /api/experimental/persistent-pty/{ptyID}/connect-token
+ file:
+ methods:
+ read: get /api/fs/read/{path}
+ list: get /api/fs/list
+ find: get /api/fs/find
+ write: post /api/experimental/fs/write
+ form:
+ methods:
+ list: get /api/form
+ generate:
+ methods:
+ text: post /api/experimental/generate
+ integration:
+ methods:
+ list: get /api/integration
+ get: get /api/integration/{integrationID}
+ subresources:
+ wellknown:
+ methods:
+ add: post /api/experimental/integration/wellknown
+ connect:
+ methods:
+ key: post /api/integration/{integrationID}/connect/key
+ external: post /api/integration/{integrationID}/connect/external
+ oauth:
+ methods:
+ connect: post /api/integration/{integrationID}/connect/oauth
+ status: get /api/integration/{integrationID}/connect/oauth/{attemptID}
+ complete: post /api/integration/{integrationID}/connect/oauth/{attemptID}/complete
+ cancel: delete /api/integration/{integrationID}/connect/oauth/{attemptID}
+ command:
+ methods:
+ connect: post /api/integration/{integrationID}/connect/command
+ status: get /api/integration/{integrationID}/connect/command/{attemptID}
+ cancel: delete /api/integration/{integrationID}/connect/command/{attemptID}
+ location:
+ methods:
+ get: get /api/location
+ reload: post /api/location/reload
+ mcp:
+ methods:
+ list: get /api/mcp
+ add: put /api/experimental/mcp/{server}
+ remove: delete /api/experimental/mcp/{server}
+ connect: post /api/experimental/mcp/{server}/connect
+ disconnect: post /api/experimental/mcp/{server}/disconnect
+ subresources:
+ resource:
+ methods:
+ catalog: get /api/mcp/resource
+ message:
+ methods:
+ list: get /api/session/{sessionID}/message
+ migration:
+ subresources:
+ v1:
+ methods:
+ status: get /api/experimental/migration/v1
+ model:
+ methods:
+ list: get /api/model
+ default: get /api/model/default
+ permission:
+ methods:
+ create: post /api/session/{sessionID}/permission
+ list: get /api/session/{sessionID}/permission
+ get: get /api/session/{sessionID}/permission/{requestID}
+ reply: post /api/session/{sessionID}/permission/{requestID}/reply
+ subresources:
+ request_:
+ methods:
+ list: get /api/permission/request
+ saved:
+ methods:
+ list: get /api/permission/saved
+ update: patch /api/permission/saved/{id}
+ remove: delete /api/permission/saved/{id}
+ plugin:
+ methods:
+ list: get /api/plugin
+ check: post /api/plugin/check
+ update: post /api/plugin/update
+ project:
+ methods:
+ list: get /api/project
+ update: patch /api/project/{projectID}
+ provider:
+ methods:
+ list: get /api/provider
+ get: get /api/provider/{providerID}
+ pty:
+ methods:
+ list: get /api/pty
+ create: post /api/pty
+ get: get /api/pty/{ptyID}
+ update: put /api/pty/{ptyID}
+ remove: delete /api/pty/{ptyID}
+ subresources:
+ connect:
+ methods:
+ token: post /api/pty/{ptyID}/connect-token
+ reference:
+ methods:
+ list: get /api/reference
+ rpc:
+ methods:
+ call: post /api/rpc/{rpcID}/{method}
+ server:
+ methods:
+ info: get /api/info
+ pair: post /api/pair
+ connect: get /auth/connect/{code}
+ session:
+ methods:
+ list: get /api/session
+ stats: get /api/experimental/session/stats
+ create: post /api/session
+ import_: post /api/experimental/session/import
+ export: get /api/experimental/session/{sessionID}/export
+ active: get /api/session/active
+ get: get /api/session/{sessionID}
+ remove: delete /api/session/{sessionID}
+ fork: post /api/session/{sessionID}/fork
+ switch_agent: post /api/session/{sessionID}/agent
+ switch_model: post /api/session/{sessionID}/model
+ update: patch /api/session/{sessionID}
+ move: post /api/session/{sessionID}/move
+ prompt: post /api/session/{sessionID}/prompt
+ command: post /api/session/{sessionID}/command
+ skill: post /api/experimental/session/{sessionID}/skill
+ synthetic: post /api/session/{sessionID}/synthetic
+ shell: post /api/session/{sessionID}/shell
+ compact: post /api/session/{sessionID}/compact
+ wait: post /api/experimental/session/{sessionID}/wait
+ context: get /api/session/{sessionID}/context
+ diff: get /api/session/{sessionID}/diff
+ generate: post /api/session/{sessionID}/generate
+ log: get /api/experimental/session/{sessionID}/log
+ interrupt: post /api/session/{sessionID}/interrupt
+ background: post /api/session/{sessionID}/background
+ environment: put /api/session/{sessionID}/environment
+ view: post /api/session/{sessionID}/view
+ subresources:
+ revert:
+ methods:
+ stage: post /api/session/{sessionID}/revert/stage
+ clear: delete /api/session/{sessionID}/revert
+ commit: post /api/session/{sessionID}/revert/commit
+ inbox:
+ methods:
+ list: get /api/session/{sessionID}/inbox
+ cancel: delete /api/session/{sessionID}/inbox/{inboxID}
+ update: patch /api/session/{sessionID}/inbox/{inboxID}
+ instructions:
+ subresources:
+ entry:
+ methods:
+ list: get /api/experimental/session/{sessionID}/instructions/entries
+ put: put /api/experimental/session/{sessionID}/instructions/entries/{key}
+ remove: delete /api/experimental/session/{sessionID}/instructions/entries/{key}
+ message:
+ methods:
+ get: get /api/session/{sessionID}/message/{messageID}
+ form:
+ methods:
+ list: get /api/session/{sessionID}/form
+ create: post /api/session/{sessionID}/form
+ get: get /api/session/{sessionID}/form/{formID}
+ reply: post /api/session/{sessionID}/form/{formID}/reply
+ cancel: delete /api/session/{sessionID}/form/{formID}
+ shell:
+ methods:
+ list: get /api/shell
+ create: post /api/shell
+ get: get /api/shell/{id}
+ output: get /api/shell/{id}/output
+ remove: delete /api/shell/{id}
+ skill:
+ methods:
+ list: get /api/skill
+ vcs:
+ methods:
+ init: post /api/vcs/init
+ get: get /api/vcs
+ base: get /api/vcs/base
+ status: get /api/vcs/status
+ diff: get /api/vcs/diff
+ subresources:
+ branch:
+ methods:
+ list: get /api/vcs/branch
+ voice:
+ methods:
+ session: post /api/experimental/voice/session
+ websearch:
+ methods:
+ providers: get /api/websearch/provider
+ query: post /api/websearch
+ worktree:
+ methods:
+ list: get /api/worktree
+ create: post /api/worktree
+ remove: delete /api/worktree
+ refresh: post /api/worktree/refresh
diff --git a/tests/__init__.py b/tests/__init__.py
index e69de29..639750b 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/__init__.py b/tests/api_resources/__init__.py
new file mode 100644
index 0000000..639750b
--- /dev/null
+++ b/tests/api_resources/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/debug/__init__.py b/tests/api_resources/debug/__init__.py
new file mode 100644
index 0000000..639750b
--- /dev/null
+++ b/tests/api_resources/debug/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/debug/test_location.py b/tests/api_resources/debug/test_location.py
new file mode 100644
index 0000000..da4ccc6
--- /dev/null
+++ b/tests/api_resources/debug/test_location.py
@@ -0,0 +1,142 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.debug import LocationListResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestLocation:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ location = client.debug.location.list()
+ assert_matches_type(LocationListResponse, location, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.debug.location.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ location = response.parse()
+ assert_matches_type(LocationListResponse, location, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.debug.location.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ location = response.parse()
+ assert_matches_type(LocationListResponse, location, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_evict(self, client: Opencode) -> None:
+ location = client.debug.location.evict()
+ assert location is None
+
+ @parametrize
+ def test_method_evict_with_all_params(self, client: Opencode) -> None:
+ location = client.debug.location.evict(
+ location={
+ "directory": "string",
+ },
+ )
+ assert location is None
+
+ @parametrize
+ def test_raw_response_evict(self, client: Opencode) -> None:
+ response = client.debug.location.with_raw_response.evict()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ location = response.parse()
+ assert location is None
+
+ @parametrize
+ def test_streaming_response_evict(self, client: Opencode) -> None:
+ with client.debug.location.with_streaming_response.evict() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ location = response.parse()
+ assert location is None
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncLocation:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ location = await async_client.debug.location.list()
+ assert_matches_type(LocationListResponse, location, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.debug.location.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ location = await response.parse()
+ assert_matches_type(LocationListResponse, location, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.debug.location.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ location = await response.parse()
+ assert_matches_type(LocationListResponse, location, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_evict(self, async_client: AsyncOpencode) -> None:
+ location = await async_client.debug.location.evict()
+ assert location is None
+
+ @parametrize
+ async def test_method_evict_with_all_params(self, async_client: AsyncOpencode) -> None:
+ location = await async_client.debug.location.evict(
+ location={
+ "directory": "string",
+ },
+ )
+ assert location is None
+
+ @parametrize
+ async def test_raw_response_evict(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.debug.location.with_raw_response.evict()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ location = await response.parse()
+ assert location is None
+
+ @parametrize
+ async def test_streaming_response_evict(self, async_client: AsyncOpencode) -> None:
+ async with async_client.debug.location.with_streaming_response.evict() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ location = await response.parse()
+ assert location is None
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/experimental/__init__.py b/tests/api_resources/experimental/__init__.py
new file mode 100644
index 0000000..639750b
--- /dev/null
+++ b/tests/api_resources/experimental/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/experimental/test_persistent_pty.py b/tests/api_resources/experimental/test_persistent_pty.py
new file mode 100644
index 0000000..39765c0
--- /dev/null
+++ b/tests/api_resources/experimental/test_persistent_pty.py
@@ -0,0 +1,885 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.experimental import (
+ PersistentPtyGetResponse,
+ PersistentPtyListResponse,
+ PersistentPtyReadResponse,
+ PersistentPtyCreateResponse,
+ PersistentPtyUpdateResponse,
+ PersistentPtyHandoffResponse,
+ PersistentPtySnapshotResponse,
+ PersistentPtyConnectTokenResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestPersistentPty:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_create(self, client: Opencode) -> None:
+ persistent_pty = client.experimental.persistent_pty.create(
+ session_id={},
+ args=["string"],
+ env={"foo": "string"},
+ title="title",
+ )
+ assert_matches_type(PersistentPtyCreateResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_method_create_with_all_params(self, client: Opencode) -> None:
+ persistent_pty = client.experimental.persistent_pty.create(
+ session_id={},
+ args=["string"],
+ env={"foo": "string"},
+ title="title",
+ command="command",
+ cwd="cwd",
+ size={
+ "cols": 0,
+ "rows": 0,
+ },
+ )
+ assert_matches_type(PersistentPtyCreateResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_raw_response_create(self, client: Opencode) -> None:
+ response = client.experimental.persistent_pty.with_raw_response.create(
+ session_id={},
+ args=["string"],
+ env={"foo": "string"},
+ title="title",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = response.parse()
+ assert_matches_type(PersistentPtyCreateResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create(self, client: Opencode) -> None:
+ with client.experimental.persistent_pty.with_streaming_response.create(
+ session_id={},
+ args=["string"],
+ env={"foo": "string"},
+ title="title",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = response.parse()
+ assert_matches_type(PersistentPtyCreateResponse, persistent_pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_create(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.experimental.persistent_pty.with_raw_response.create(
+ session_id="",
+ args=["string"],
+ env={"foo": "string"},
+ title="title",
+ )
+
+ @parametrize
+ def test_method_update(self, client: Opencode) -> None:
+ persistent_pty = client.experimental.persistent_pty.update(
+ pty_id={},
+ size={
+ "cols": 0,
+ "rows": 0,
+ },
+ )
+ assert_matches_type(PersistentPtyUpdateResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_method_update_with_all_params(self, client: Opencode) -> None:
+ persistent_pty = client.experimental.persistent_pty.update(
+ pty_id={},
+ size={
+ "cols": 0,
+ "rows": 0,
+ },
+ attachment_id="attachmentID",
+ )
+ assert_matches_type(PersistentPtyUpdateResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_raw_response_update(self, client: Opencode) -> None:
+ response = client.experimental.persistent_pty.with_raw_response.update(
+ pty_id={},
+ size={
+ "cols": 0,
+ "rows": 0,
+ },
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = response.parse()
+ assert_matches_type(PersistentPtyUpdateResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_streaming_response_update(self, client: Opencode) -> None:
+ with client.experimental.persistent_pty.with_streaming_response.update(
+ pty_id={},
+ size={
+ "cols": 0,
+ "rows": 0,
+ },
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = response.parse()
+ assert_matches_type(PersistentPtyUpdateResponse, persistent_pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_update(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `pty_id` but received ''"):
+ client.experimental.persistent_pty.with_raw_response.update(
+ pty_id="",
+ size={
+ "cols": 0,
+ "rows": 0,
+ },
+ )
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ persistent_pty = client.experimental.persistent_pty.list(
+ {},
+ )
+ assert_matches_type(PersistentPtyListResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.experimental.persistent_pty.with_raw_response.list(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = response.parse()
+ assert_matches_type(PersistentPtyListResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.experimental.persistent_pty.with_streaming_response.list(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = response.parse()
+ assert_matches_type(PersistentPtyListResponse, persistent_pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_list(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.experimental.persistent_pty.with_raw_response.list(
+ "",
+ )
+
+ @parametrize
+ def test_method_connect_token(self, client: Opencode) -> None:
+ persistent_pty = client.experimental.persistent_pty.connect_token(
+ pty_id={},
+ )
+ assert_matches_type(PersistentPtyConnectTokenResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_method_connect_token_with_all_params(self, client: Opencode) -> None:
+ persistent_pty = client.experimental.persistent_pty.connect_token(
+ pty_id={},
+ x_opencode_ticket="x-opencode-ticket",
+ )
+ assert_matches_type(PersistentPtyConnectTokenResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_raw_response_connect_token(self, client: Opencode) -> None:
+ response = client.experimental.persistent_pty.with_raw_response.connect_token(
+ pty_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = response.parse()
+ assert_matches_type(PersistentPtyConnectTokenResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_streaming_response_connect_token(self, client: Opencode) -> None:
+ with client.experimental.persistent_pty.with_streaming_response.connect_token(
+ pty_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = response.parse()
+ assert_matches_type(PersistentPtyConnectTokenResponse, persistent_pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_connect_token(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `pty_id` but received ''"):
+ client.experimental.persistent_pty.with_raw_response.connect_token(
+ pty_id="",
+ )
+
+ @parametrize
+ def test_method_get(self, client: Opencode) -> None:
+ persistent_pty = client.experimental.persistent_pty.get(
+ {},
+ )
+ assert_matches_type(PersistentPtyGetResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Opencode) -> None:
+ response = client.experimental.persistent_pty.with_raw_response.get(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = response.parse()
+ assert_matches_type(PersistentPtyGetResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Opencode) -> None:
+ with client.experimental.persistent_pty.with_streaming_response.get(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = response.parse()
+ assert_matches_type(PersistentPtyGetResponse, persistent_pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_get(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `pty_id` but received ''"):
+ client.experimental.persistent_pty.with_raw_response.get(
+ "",
+ )
+
+ @parametrize
+ def test_method_handoff(self, client: Opencode) -> None:
+ persistent_pty = client.experimental.persistent_pty.handoff()
+ assert_matches_type(PersistentPtyHandoffResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_raw_response_handoff(self, client: Opencode) -> None:
+ response = client.experimental.persistent_pty.with_raw_response.handoff()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = response.parse()
+ assert_matches_type(PersistentPtyHandoffResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_streaming_response_handoff(self, client: Opencode) -> None:
+ with client.experimental.persistent_pty.with_streaming_response.handoff() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = response.parse()
+ assert_matches_type(PersistentPtyHandoffResponse, persistent_pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_read(self, client: Opencode) -> None:
+ persistent_pty = client.experimental.persistent_pty.read(
+ session_id={},
+ )
+ assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_method_read_with_all_params(self, client: Opencode) -> None:
+ persistent_pty = client.experimental.persistent_pty.read(
+ session_id={},
+ lines={},
+ )
+ assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_raw_response_read(self, client: Opencode) -> None:
+ response = client.experimental.persistent_pty.with_raw_response.read(
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = response.parse()
+ assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_streaming_response_read(self, client: Opencode) -> None:
+ with client.experimental.persistent_pty.with_streaming_response.read(
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = response.parse()
+ assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_read(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.experimental.persistent_pty.with_raw_response.read(
+ session_id="",
+ )
+
+ @parametrize
+ def test_method_remove(self, client: Opencode) -> None:
+ persistent_pty = client.experimental.persistent_pty.remove(
+ {},
+ )
+ assert persistent_pty is None
+
+ @parametrize
+ def test_raw_response_remove(self, client: Opencode) -> None:
+ response = client.experimental.persistent_pty.with_raw_response.remove(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = response.parse()
+ assert persistent_pty is None
+
+ @parametrize
+ def test_streaming_response_remove(self, client: Opencode) -> None:
+ with client.experimental.persistent_pty.with_streaming_response.remove(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = response.parse()
+ assert persistent_pty is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_remove(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `pty_id` but received ''"):
+ client.experimental.persistent_pty.with_raw_response.remove(
+ "",
+ )
+
+ @parametrize
+ def test_method_shutdown(self, client: Opencode) -> None:
+ persistent_pty = client.experimental.persistent_pty.shutdown()
+ assert persistent_pty is None
+
+ @parametrize
+ def test_raw_response_shutdown(self, client: Opencode) -> None:
+ response = client.experimental.persistent_pty.with_raw_response.shutdown()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = response.parse()
+ assert persistent_pty is None
+
+ @parametrize
+ def test_streaming_response_shutdown(self, client: Opencode) -> None:
+ with client.experimental.persistent_pty.with_streaming_response.shutdown() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = response.parse()
+ assert persistent_pty is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_snapshot(self, client: Opencode) -> None:
+ persistent_pty = client.experimental.persistent_pty.snapshot(
+ {},
+ )
+ assert_matches_type(PersistentPtySnapshotResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_raw_response_snapshot(self, client: Opencode) -> None:
+ response = client.experimental.persistent_pty.with_raw_response.snapshot(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = response.parse()
+ assert_matches_type(PersistentPtySnapshotResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ def test_streaming_response_snapshot(self, client: Opencode) -> None:
+ with client.experimental.persistent_pty.with_streaming_response.snapshot(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = response.parse()
+ assert_matches_type(PersistentPtySnapshotResponse, persistent_pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_snapshot(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `pty_id` but received ''"):
+ client.experimental.persistent_pty.with_raw_response.snapshot(
+ "",
+ )
+
+
+class TestAsyncPersistentPty:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_create(self, async_client: AsyncOpencode) -> None:
+ persistent_pty = await async_client.experimental.persistent_pty.create(
+ session_id={},
+ args=["string"],
+ env={"foo": "string"},
+ title="title",
+ )
+ assert_matches_type(PersistentPtyCreateResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
+ persistent_pty = await async_client.experimental.persistent_pty.create(
+ session_id={},
+ args=["string"],
+ env={"foo": "string"},
+ title="title",
+ command="command",
+ cwd="cwd",
+ size={
+ "cols": 0,
+ "rows": 0,
+ },
+ )
+ assert_matches_type(PersistentPtyCreateResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.experimental.persistent_pty.with_raw_response.create(
+ session_id={},
+ args=["string"],
+ env={"foo": "string"},
+ title="title",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = await response.parse()
+ assert_matches_type(PersistentPtyCreateResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
+ async with async_client.experimental.persistent_pty.with_streaming_response.create(
+ session_id={},
+ args=["string"],
+ env={"foo": "string"},
+ title="title",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = await response.parse()
+ assert_matches_type(PersistentPtyCreateResponse, persistent_pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_create(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.experimental.persistent_pty.with_raw_response.create(
+ session_id="",
+ args=["string"],
+ env={"foo": "string"},
+ title="title",
+ )
+
+ @parametrize
+ async def test_method_update(self, async_client: AsyncOpencode) -> None:
+ persistent_pty = await async_client.experimental.persistent_pty.update(
+ pty_id={},
+ size={
+ "cols": 0,
+ "rows": 0,
+ },
+ )
+ assert_matches_type(PersistentPtyUpdateResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncOpencode) -> None:
+ persistent_pty = await async_client.experimental.persistent_pty.update(
+ pty_id={},
+ size={
+ "cols": 0,
+ "rows": 0,
+ },
+ attachment_id="attachmentID",
+ )
+ assert_matches_type(PersistentPtyUpdateResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.experimental.persistent_pty.with_raw_response.update(
+ pty_id={},
+ size={
+ "cols": 0,
+ "rows": 0,
+ },
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = await response.parse()
+ assert_matches_type(PersistentPtyUpdateResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncOpencode) -> None:
+ async with async_client.experimental.persistent_pty.with_streaming_response.update(
+ pty_id={},
+ size={
+ "cols": 0,
+ "rows": 0,
+ },
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = await response.parse()
+ assert_matches_type(PersistentPtyUpdateResponse, persistent_pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `pty_id` but received ''"):
+ await async_client.experimental.persistent_pty.with_raw_response.update(
+ pty_id="",
+ size={
+ "cols": 0,
+ "rows": 0,
+ },
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ persistent_pty = await async_client.experimental.persistent_pty.list(
+ {},
+ )
+ assert_matches_type(PersistentPtyListResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.experimental.persistent_pty.with_raw_response.list(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = await response.parse()
+ assert_matches_type(PersistentPtyListResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.experimental.persistent_pty.with_streaming_response.list(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = await response.parse()
+ assert_matches_type(PersistentPtyListResponse, persistent_pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_list(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.experimental.persistent_pty.with_raw_response.list(
+ "",
+ )
+
+ @parametrize
+ async def test_method_connect_token(self, async_client: AsyncOpencode) -> None:
+ persistent_pty = await async_client.experimental.persistent_pty.connect_token(
+ pty_id={},
+ )
+ assert_matches_type(PersistentPtyConnectTokenResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_method_connect_token_with_all_params(self, async_client: AsyncOpencode) -> None:
+ persistent_pty = await async_client.experimental.persistent_pty.connect_token(
+ pty_id={},
+ x_opencode_ticket="x-opencode-ticket",
+ )
+ assert_matches_type(PersistentPtyConnectTokenResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_raw_response_connect_token(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.experimental.persistent_pty.with_raw_response.connect_token(
+ pty_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = await response.parse()
+ assert_matches_type(PersistentPtyConnectTokenResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_connect_token(self, async_client: AsyncOpencode) -> None:
+ async with async_client.experimental.persistent_pty.with_streaming_response.connect_token(
+ pty_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = await response.parse()
+ assert_matches_type(PersistentPtyConnectTokenResponse, persistent_pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_connect_token(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `pty_id` but received ''"):
+ await async_client.experimental.persistent_pty.with_raw_response.connect_token(
+ pty_id="",
+ )
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncOpencode) -> None:
+ persistent_pty = await async_client.experimental.persistent_pty.get(
+ {},
+ )
+ assert_matches_type(PersistentPtyGetResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.experimental.persistent_pty.with_raw_response.get(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = await response.parse()
+ assert_matches_type(PersistentPtyGetResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
+ async with async_client.experimental.persistent_pty.with_streaming_response.get(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = await response.parse()
+ assert_matches_type(PersistentPtyGetResponse, persistent_pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_get(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `pty_id` but received ''"):
+ await async_client.experimental.persistent_pty.with_raw_response.get(
+ "",
+ )
+
+ @parametrize
+ async def test_method_handoff(self, async_client: AsyncOpencode) -> None:
+ persistent_pty = await async_client.experimental.persistent_pty.handoff()
+ assert_matches_type(PersistentPtyHandoffResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_raw_response_handoff(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.experimental.persistent_pty.with_raw_response.handoff()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = await response.parse()
+ assert_matches_type(PersistentPtyHandoffResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_handoff(self, async_client: AsyncOpencode) -> None:
+ async with async_client.experimental.persistent_pty.with_streaming_response.handoff() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = await response.parse()
+ assert_matches_type(PersistentPtyHandoffResponse, persistent_pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_read(self, async_client: AsyncOpencode) -> None:
+ persistent_pty = await async_client.experimental.persistent_pty.read(
+ session_id={},
+ )
+ assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_method_read_with_all_params(self, async_client: AsyncOpencode) -> None:
+ persistent_pty = await async_client.experimental.persistent_pty.read(
+ session_id={},
+ lines={},
+ )
+ assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_raw_response_read(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.experimental.persistent_pty.with_raw_response.read(
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = await response.parse()
+ assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_read(self, async_client: AsyncOpencode) -> None:
+ async with async_client.experimental.persistent_pty.with_streaming_response.read(
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = await response.parse()
+ assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_read(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.experimental.persistent_pty.with_raw_response.read(
+ session_id="",
+ )
+
+ @parametrize
+ async def test_method_remove(self, async_client: AsyncOpencode) -> None:
+ persistent_pty = await async_client.experimental.persistent_pty.remove(
+ {},
+ )
+ assert persistent_pty is None
+
+ @parametrize
+ async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.experimental.persistent_pty.with_raw_response.remove(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = await response.parse()
+ assert persistent_pty is None
+
+ @parametrize
+ async def test_streaming_response_remove(self, async_client: AsyncOpencode) -> None:
+ async with async_client.experimental.persistent_pty.with_streaming_response.remove(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = await response.parse()
+ assert persistent_pty is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_remove(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `pty_id` but received ''"):
+ await async_client.experimental.persistent_pty.with_raw_response.remove(
+ "",
+ )
+
+ @parametrize
+ async def test_method_shutdown(self, async_client: AsyncOpencode) -> None:
+ persistent_pty = await async_client.experimental.persistent_pty.shutdown()
+ assert persistent_pty is None
+
+ @parametrize
+ async def test_raw_response_shutdown(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.experimental.persistent_pty.with_raw_response.shutdown()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = await response.parse()
+ assert persistent_pty is None
+
+ @parametrize
+ async def test_streaming_response_shutdown(self, async_client: AsyncOpencode) -> None:
+ async with async_client.experimental.persistent_pty.with_streaming_response.shutdown() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = await response.parse()
+ assert persistent_pty is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_snapshot(self, async_client: AsyncOpencode) -> None:
+ persistent_pty = await async_client.experimental.persistent_pty.snapshot(
+ {},
+ )
+ assert_matches_type(PersistentPtySnapshotResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_raw_response_snapshot(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.experimental.persistent_pty.with_raw_response.snapshot(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ persistent_pty = await response.parse()
+ assert_matches_type(PersistentPtySnapshotResponse, persistent_pty, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_snapshot(self, async_client: AsyncOpencode) -> None:
+ async with async_client.experimental.persistent_pty.with_streaming_response.snapshot(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ persistent_pty = await response.parse()
+ assert_matches_type(PersistentPtySnapshotResponse, persistent_pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_snapshot(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `pty_id` but received ''"):
+ await async_client.experimental.persistent_pty.with_raw_response.snapshot(
+ "",
+ )
diff --git a/tests/api_resources/integration/__init__.py b/tests/api_resources/integration/__init__.py
new file mode 100644
index 0000000..639750b
--- /dev/null
+++ b/tests/api_resources/integration/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/integration/test_command.py b/tests/api_resources/integration/test_command.py
new file mode 100644
index 0000000..0cc83d0
--- /dev/null
+++ b/tests/api_resources/integration/test_command.py
@@ -0,0 +1,368 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.integration import CommandStatusResponse, CommandConnectResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestCommand:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_cancel(self, client: Opencode) -> None:
+ command = client.integration.command.cancel(
+ attempt_id={},
+ integration_id={},
+ )
+ assert command is None
+
+ @parametrize
+ def test_method_cancel_with_all_params(self, client: Opencode) -> None:
+ command = client.integration.command.cancel(
+ attempt_id={},
+ integration_id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert command is None
+
+ @parametrize
+ def test_raw_response_cancel(self, client: Opencode) -> None:
+ response = client.integration.command.with_raw_response.cancel(
+ attempt_id={},
+ integration_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ command = response.parse()
+ assert command is None
+
+ @parametrize
+ def test_streaming_response_cancel(self, client: Opencode) -> None:
+ with client.integration.command.with_streaming_response.cancel(
+ attempt_id={},
+ integration_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ command = response.parse()
+ assert command is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_cancel(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ client.integration.command.with_raw_response.cancel(
+ attempt_id={},
+ integration_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
+ client.integration.command.with_raw_response.cancel(
+ attempt_id="",
+ integration_id={},
+ )
+
+ @parametrize
+ def test_method_connect(self, client: Opencode) -> None:
+ command = client.integration.command.connect(
+ integration_id={},
+ method_id={},
+ )
+ assert_matches_type(CommandConnectResponse, command, path=["response"])
+
+ @parametrize
+ def test_method_connect_with_all_params(self, client: Opencode) -> None:
+ command = client.integration.command.connect(
+ integration_id={},
+ method_id={},
+ label="label",
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(CommandConnectResponse, command, path=["response"])
+
+ @parametrize
+ def test_raw_response_connect(self, client: Opencode) -> None:
+ response = client.integration.command.with_raw_response.connect(
+ integration_id={},
+ method_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ command = response.parse()
+ assert_matches_type(CommandConnectResponse, command, path=["response"])
+
+ @parametrize
+ def test_streaming_response_connect(self, client: Opencode) -> None:
+ with client.integration.command.with_streaming_response.connect(
+ integration_id={},
+ method_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ command = response.parse()
+ assert_matches_type(CommandConnectResponse, command, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_connect(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ client.integration.command.with_raw_response.connect(
+ integration_id="",
+ method_id={},
+ )
+
+ @parametrize
+ def test_method_status(self, client: Opencode) -> None:
+ command = client.integration.command.status(
+ attempt_id={},
+ integration_id={},
+ )
+ assert_matches_type(CommandStatusResponse, command, path=["response"])
+
+ @parametrize
+ def test_method_status_with_all_params(self, client: Opencode) -> None:
+ command = client.integration.command.status(
+ attempt_id={},
+ integration_id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(CommandStatusResponse, command, path=["response"])
+
+ @parametrize
+ def test_raw_response_status(self, client: Opencode) -> None:
+ response = client.integration.command.with_raw_response.status(
+ attempt_id={},
+ integration_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ command = response.parse()
+ assert_matches_type(CommandStatusResponse, command, path=["response"])
+
+ @parametrize
+ def test_streaming_response_status(self, client: Opencode) -> None:
+ with client.integration.command.with_streaming_response.status(
+ attempt_id={},
+ integration_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ command = response.parse()
+ assert_matches_type(CommandStatusResponse, command, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_status(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ client.integration.command.with_raw_response.status(
+ attempt_id={},
+ integration_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
+ client.integration.command.with_raw_response.status(
+ attempt_id="",
+ integration_id={},
+ )
+
+
+class TestAsyncCommand:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_cancel(self, async_client: AsyncOpencode) -> None:
+ command = await async_client.integration.command.cancel(
+ attempt_id={},
+ integration_id={},
+ )
+ assert command is None
+
+ @parametrize
+ async def test_method_cancel_with_all_params(self, async_client: AsyncOpencode) -> None:
+ command = await async_client.integration.command.cancel(
+ attempt_id={},
+ integration_id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert command is None
+
+ @parametrize
+ async def test_raw_response_cancel(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.integration.command.with_raw_response.cancel(
+ attempt_id={},
+ integration_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ command = await response.parse()
+ assert command is None
+
+ @parametrize
+ async def test_streaming_response_cancel(self, async_client: AsyncOpencode) -> None:
+ async with async_client.integration.command.with_streaming_response.cancel(
+ attempt_id={},
+ integration_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ command = await response.parse()
+ assert command is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_cancel(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ await async_client.integration.command.with_raw_response.cancel(
+ attempt_id={},
+ integration_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
+ await async_client.integration.command.with_raw_response.cancel(
+ attempt_id="",
+ integration_id={},
+ )
+
+ @parametrize
+ async def test_method_connect(self, async_client: AsyncOpencode) -> None:
+ command = await async_client.integration.command.connect(
+ integration_id={},
+ method_id={},
+ )
+ assert_matches_type(CommandConnectResponse, command, path=["response"])
+
+ @parametrize
+ async def test_method_connect_with_all_params(self, async_client: AsyncOpencode) -> None:
+ command = await async_client.integration.command.connect(
+ integration_id={},
+ method_id={},
+ label="label",
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(CommandConnectResponse, command, path=["response"])
+
+ @parametrize
+ async def test_raw_response_connect(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.integration.command.with_raw_response.connect(
+ integration_id={},
+ method_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ command = await response.parse()
+ assert_matches_type(CommandConnectResponse, command, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_connect(self, async_client: AsyncOpencode) -> None:
+ async with async_client.integration.command.with_streaming_response.connect(
+ integration_id={},
+ method_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ command = await response.parse()
+ assert_matches_type(CommandConnectResponse, command, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_connect(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ await async_client.integration.command.with_raw_response.connect(
+ integration_id="",
+ method_id={},
+ )
+
+ @parametrize
+ async def test_method_status(self, async_client: AsyncOpencode) -> None:
+ command = await async_client.integration.command.status(
+ attempt_id={},
+ integration_id={},
+ )
+ assert_matches_type(CommandStatusResponse, command, path=["response"])
+
+ @parametrize
+ async def test_method_status_with_all_params(self, async_client: AsyncOpencode) -> None:
+ command = await async_client.integration.command.status(
+ attempt_id={},
+ integration_id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(CommandStatusResponse, command, path=["response"])
+
+ @parametrize
+ async def test_raw_response_status(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.integration.command.with_raw_response.status(
+ attempt_id={},
+ integration_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ command = await response.parse()
+ assert_matches_type(CommandStatusResponse, command, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_status(self, async_client: AsyncOpencode) -> None:
+ async with async_client.integration.command.with_streaming_response.status(
+ attempt_id={},
+ integration_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ command = await response.parse()
+ assert_matches_type(CommandStatusResponse, command, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_status(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ await async_client.integration.command.with_raw_response.status(
+ attempt_id={},
+ integration_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
+ await async_client.integration.command.with_raw_response.status(
+ attempt_id="",
+ integration_id={},
+ )
diff --git a/tests/api_resources/integration/test_connect.py b/tests/api_resources/integration/test_connect.py
new file mode 100644
index 0000000..db30d20
--- /dev/null
+++ b/tests/api_resources/integration/test_connect.py
@@ -0,0 +1,242 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestConnect:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_external(self, client: Opencode) -> None:
+ connect = client.integration.connect.external(
+ integration_id={},
+ method_id={},
+ )
+ assert connect is None
+
+ @parametrize
+ def test_method_external_with_all_params(self, client: Opencode) -> None:
+ connect = client.integration.connect.external(
+ integration_id={},
+ method_id={},
+ answer={},
+ label="label",
+ location={
+ "directory": "string",
+ },
+ )
+ assert connect is None
+
+ @parametrize
+ def test_raw_response_external(self, client: Opencode) -> None:
+ response = client.integration.connect.with_raw_response.external(
+ integration_id={},
+ method_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ connect = response.parse()
+ assert connect is None
+
+ @parametrize
+ def test_streaming_response_external(self, client: Opencode) -> None:
+ with client.integration.connect.with_streaming_response.external(
+ integration_id={},
+ method_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ connect = response.parse()
+ assert connect is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_external(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ client.integration.connect.with_raw_response.external(
+ integration_id="",
+ method_id={},
+ )
+
+ @parametrize
+ def test_method_key(self, client: Opencode) -> None:
+ connect = client.integration.connect.key(
+ integration_id={},
+ key="key",
+ )
+ assert connect is None
+
+ @parametrize
+ def test_method_key_with_all_params(self, client: Opencode) -> None:
+ connect = client.integration.connect.key(
+ integration_id={},
+ key="key",
+ answer={},
+ label="label",
+ location={
+ "directory": "string",
+ },
+ )
+ assert connect is None
+
+ @parametrize
+ def test_raw_response_key(self, client: Opencode) -> None:
+ response = client.integration.connect.with_raw_response.key(
+ integration_id={},
+ key="key",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ connect = response.parse()
+ assert connect is None
+
+ @parametrize
+ def test_streaming_response_key(self, client: Opencode) -> None:
+ with client.integration.connect.with_streaming_response.key(
+ integration_id={},
+ key="key",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ connect = response.parse()
+ assert connect is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_key(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ client.integration.connect.with_raw_response.key(
+ integration_id="",
+ key="key",
+ )
+
+
+class TestAsyncConnect:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_external(self, async_client: AsyncOpencode) -> None:
+ connect = await async_client.integration.connect.external(
+ integration_id={},
+ method_id={},
+ )
+ assert connect is None
+
+ @parametrize
+ async def test_method_external_with_all_params(self, async_client: AsyncOpencode) -> None:
+ connect = await async_client.integration.connect.external(
+ integration_id={},
+ method_id={},
+ answer={},
+ label="label",
+ location={
+ "directory": "string",
+ },
+ )
+ assert connect is None
+
+ @parametrize
+ async def test_raw_response_external(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.integration.connect.with_raw_response.external(
+ integration_id={},
+ method_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ connect = await response.parse()
+ assert connect is None
+
+ @parametrize
+ async def test_streaming_response_external(self, async_client: AsyncOpencode) -> None:
+ async with async_client.integration.connect.with_streaming_response.external(
+ integration_id={},
+ method_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ connect = await response.parse()
+ assert connect is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_external(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ await async_client.integration.connect.with_raw_response.external(
+ integration_id="",
+ method_id={},
+ )
+
+ @parametrize
+ async def test_method_key(self, async_client: AsyncOpencode) -> None:
+ connect = await async_client.integration.connect.key(
+ integration_id={},
+ key="key",
+ )
+ assert connect is None
+
+ @parametrize
+ async def test_method_key_with_all_params(self, async_client: AsyncOpencode) -> None:
+ connect = await async_client.integration.connect.key(
+ integration_id={},
+ key="key",
+ answer={},
+ label="label",
+ location={
+ "directory": "string",
+ },
+ )
+ assert connect is None
+
+ @parametrize
+ async def test_raw_response_key(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.integration.connect.with_raw_response.key(
+ integration_id={},
+ key="key",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ connect = await response.parse()
+ assert connect is None
+
+ @parametrize
+ async def test_streaming_response_key(self, async_client: AsyncOpencode) -> None:
+ async with async_client.integration.connect.with_streaming_response.key(
+ integration_id={},
+ key="key",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ connect = await response.parse()
+ assert connect is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_key(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ await async_client.integration.connect.with_raw_response.key(
+ integration_id="",
+ key="key",
+ )
diff --git a/tests/api_resources/integration/test_oauth.py b/tests/api_resources/integration/test_oauth.py
new file mode 100644
index 0000000..6892cc7
--- /dev/null
+++ b/tests/api_resources/integration/test_oauth.py
@@ -0,0 +1,490 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.integration import OauthStatusResponse, OauthConnectResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestOauth:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_cancel(self, client: Opencode) -> None:
+ oauth = client.integration.oauth.cancel(
+ attempt_id={},
+ integration_id={},
+ )
+ assert oauth is None
+
+ @parametrize
+ def test_method_cancel_with_all_params(self, client: Opencode) -> None:
+ oauth = client.integration.oauth.cancel(
+ attempt_id={},
+ integration_id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert oauth is None
+
+ @parametrize
+ def test_raw_response_cancel(self, client: Opencode) -> None:
+ response = client.integration.oauth.with_raw_response.cancel(
+ attempt_id={},
+ integration_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ oauth = response.parse()
+ assert oauth is None
+
+ @parametrize
+ def test_streaming_response_cancel(self, client: Opencode) -> None:
+ with client.integration.oauth.with_streaming_response.cancel(
+ attempt_id={},
+ integration_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ oauth = response.parse()
+ assert oauth is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_cancel(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ client.integration.oauth.with_raw_response.cancel(
+ attempt_id={},
+ integration_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
+ client.integration.oauth.with_raw_response.cancel(
+ attempt_id="",
+ integration_id={},
+ )
+
+ @parametrize
+ def test_method_complete(self, client: Opencode) -> None:
+ oauth = client.integration.oauth.complete(
+ attempt_id={},
+ integration_id={},
+ )
+ assert oauth is None
+
+ @parametrize
+ def test_method_complete_with_all_params(self, client: Opencode) -> None:
+ oauth = client.integration.oauth.complete(
+ attempt_id={},
+ integration_id={},
+ code="code",
+ location={
+ "directory": "string",
+ },
+ )
+ assert oauth is None
+
+ @parametrize
+ def test_raw_response_complete(self, client: Opencode) -> None:
+ response = client.integration.oauth.with_raw_response.complete(
+ attempt_id={},
+ integration_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ oauth = response.parse()
+ assert oauth is None
+
+ @parametrize
+ def test_streaming_response_complete(self, client: Opencode) -> None:
+ with client.integration.oauth.with_streaming_response.complete(
+ attempt_id={},
+ integration_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ oauth = response.parse()
+ assert oauth is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_complete(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ client.integration.oauth.with_raw_response.complete(
+ attempt_id={},
+ integration_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
+ client.integration.oauth.with_raw_response.complete(
+ attempt_id="",
+ integration_id={},
+ )
+
+ @parametrize
+ def test_method_connect(self, client: Opencode) -> None:
+ oauth = client.integration.oauth.connect(
+ integration_id={},
+ method_id={},
+ )
+ assert_matches_type(OauthConnectResponse, oauth, path=["response"])
+
+ @parametrize
+ def test_method_connect_with_all_params(self, client: Opencode) -> None:
+ oauth = client.integration.oauth.connect(
+ integration_id={},
+ method_id={},
+ answer={},
+ label="label",
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(OauthConnectResponse, oauth, path=["response"])
+
+ @parametrize
+ def test_raw_response_connect(self, client: Opencode) -> None:
+ response = client.integration.oauth.with_raw_response.connect(
+ integration_id={},
+ method_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ oauth = response.parse()
+ assert_matches_type(OauthConnectResponse, oauth, path=["response"])
+
+ @parametrize
+ def test_streaming_response_connect(self, client: Opencode) -> None:
+ with client.integration.oauth.with_streaming_response.connect(
+ integration_id={},
+ method_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ oauth = response.parse()
+ assert_matches_type(OauthConnectResponse, oauth, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_connect(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ client.integration.oauth.with_raw_response.connect(
+ integration_id="",
+ method_id={},
+ )
+
+ @parametrize
+ def test_method_status(self, client: Opencode) -> None:
+ oauth = client.integration.oauth.status(
+ attempt_id={},
+ integration_id={},
+ )
+ assert_matches_type(OauthStatusResponse, oauth, path=["response"])
+
+ @parametrize
+ def test_method_status_with_all_params(self, client: Opencode) -> None:
+ oauth = client.integration.oauth.status(
+ attempt_id={},
+ integration_id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(OauthStatusResponse, oauth, path=["response"])
+
+ @parametrize
+ def test_raw_response_status(self, client: Opencode) -> None:
+ response = client.integration.oauth.with_raw_response.status(
+ attempt_id={},
+ integration_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ oauth = response.parse()
+ assert_matches_type(OauthStatusResponse, oauth, path=["response"])
+
+ @parametrize
+ def test_streaming_response_status(self, client: Opencode) -> None:
+ with client.integration.oauth.with_streaming_response.status(
+ attempt_id={},
+ integration_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ oauth = response.parse()
+ assert_matches_type(OauthStatusResponse, oauth, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_status(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ client.integration.oauth.with_raw_response.status(
+ attempt_id={},
+ integration_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
+ client.integration.oauth.with_raw_response.status(
+ attempt_id="",
+ integration_id={},
+ )
+
+
+class TestAsyncOauth:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_cancel(self, async_client: AsyncOpencode) -> None:
+ oauth = await async_client.integration.oauth.cancel(
+ attempt_id={},
+ integration_id={},
+ )
+ assert oauth is None
+
+ @parametrize
+ async def test_method_cancel_with_all_params(self, async_client: AsyncOpencode) -> None:
+ oauth = await async_client.integration.oauth.cancel(
+ attempt_id={},
+ integration_id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert oauth is None
+
+ @parametrize
+ async def test_raw_response_cancel(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.integration.oauth.with_raw_response.cancel(
+ attempt_id={},
+ integration_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ oauth = await response.parse()
+ assert oauth is None
+
+ @parametrize
+ async def test_streaming_response_cancel(self, async_client: AsyncOpencode) -> None:
+ async with async_client.integration.oauth.with_streaming_response.cancel(
+ attempt_id={},
+ integration_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ oauth = await response.parse()
+ assert oauth is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_cancel(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ await async_client.integration.oauth.with_raw_response.cancel(
+ attempt_id={},
+ integration_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
+ await async_client.integration.oauth.with_raw_response.cancel(
+ attempt_id="",
+ integration_id={},
+ )
+
+ @parametrize
+ async def test_method_complete(self, async_client: AsyncOpencode) -> None:
+ oauth = await async_client.integration.oauth.complete(
+ attempt_id={},
+ integration_id={},
+ )
+ assert oauth is None
+
+ @parametrize
+ async def test_method_complete_with_all_params(self, async_client: AsyncOpencode) -> None:
+ oauth = await async_client.integration.oauth.complete(
+ attempt_id={},
+ integration_id={},
+ code="code",
+ location={
+ "directory": "string",
+ },
+ )
+ assert oauth is None
+
+ @parametrize
+ async def test_raw_response_complete(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.integration.oauth.with_raw_response.complete(
+ attempt_id={},
+ integration_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ oauth = await response.parse()
+ assert oauth is None
+
+ @parametrize
+ async def test_streaming_response_complete(self, async_client: AsyncOpencode) -> None:
+ async with async_client.integration.oauth.with_streaming_response.complete(
+ attempt_id={},
+ integration_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ oauth = await response.parse()
+ assert oauth is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_complete(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ await async_client.integration.oauth.with_raw_response.complete(
+ attempt_id={},
+ integration_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
+ await async_client.integration.oauth.with_raw_response.complete(
+ attempt_id="",
+ integration_id={},
+ )
+
+ @parametrize
+ async def test_method_connect(self, async_client: AsyncOpencode) -> None:
+ oauth = await async_client.integration.oauth.connect(
+ integration_id={},
+ method_id={},
+ )
+ assert_matches_type(OauthConnectResponse, oauth, path=["response"])
+
+ @parametrize
+ async def test_method_connect_with_all_params(self, async_client: AsyncOpencode) -> None:
+ oauth = await async_client.integration.oauth.connect(
+ integration_id={},
+ method_id={},
+ answer={},
+ label="label",
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(OauthConnectResponse, oauth, path=["response"])
+
+ @parametrize
+ async def test_raw_response_connect(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.integration.oauth.with_raw_response.connect(
+ integration_id={},
+ method_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ oauth = await response.parse()
+ assert_matches_type(OauthConnectResponse, oauth, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_connect(self, async_client: AsyncOpencode) -> None:
+ async with async_client.integration.oauth.with_streaming_response.connect(
+ integration_id={},
+ method_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ oauth = await response.parse()
+ assert_matches_type(OauthConnectResponse, oauth, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_connect(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ await async_client.integration.oauth.with_raw_response.connect(
+ integration_id="",
+ method_id={},
+ )
+
+ @parametrize
+ async def test_method_status(self, async_client: AsyncOpencode) -> None:
+ oauth = await async_client.integration.oauth.status(
+ attempt_id={},
+ integration_id={},
+ )
+ assert_matches_type(OauthStatusResponse, oauth, path=["response"])
+
+ @parametrize
+ async def test_method_status_with_all_params(self, async_client: AsyncOpencode) -> None:
+ oauth = await async_client.integration.oauth.status(
+ attempt_id={},
+ integration_id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(OauthStatusResponse, oauth, path=["response"])
+
+ @parametrize
+ async def test_raw_response_status(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.integration.oauth.with_raw_response.status(
+ attempt_id={},
+ integration_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ oauth = await response.parse()
+ assert_matches_type(OauthStatusResponse, oauth, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_status(self, async_client: AsyncOpencode) -> None:
+ async with async_client.integration.oauth.with_streaming_response.status(
+ attempt_id={},
+ integration_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ oauth = await response.parse()
+ assert_matches_type(OauthStatusResponse, oauth, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_status(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ await async_client.integration.oauth.with_raw_response.status(
+ attempt_id={},
+ integration_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
+ await async_client.integration.oauth.with_raw_response.status(
+ attempt_id="",
+ integration_id={},
+ )
diff --git a/tests/api_resources/integration/test_wellknown.py b/tests/api_resources/integration/test_wellknown.py
new file mode 100644
index 0000000..f182de8
--- /dev/null
+++ b/tests/api_resources/integration/test_wellknown.py
@@ -0,0 +1,104 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestWellknown:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_add(self, client: Opencode) -> None:
+ wellknown = client.integration.wellknown.add(
+ url="url",
+ )
+ assert wellknown is None
+
+ @parametrize
+ def test_method_add_with_all_params(self, client: Opencode) -> None:
+ wellknown = client.integration.wellknown.add(
+ url="url",
+ location={
+ "directory": "string",
+ },
+ )
+ assert wellknown is None
+
+ @parametrize
+ def test_raw_response_add(self, client: Opencode) -> None:
+ response = client.integration.wellknown.with_raw_response.add(
+ url="url",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ wellknown = response.parse()
+ assert wellknown is None
+
+ @parametrize
+ def test_streaming_response_add(self, client: Opencode) -> None:
+ with client.integration.wellknown.with_streaming_response.add(
+ url="url",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ wellknown = response.parse()
+ assert wellknown is None
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncWellknown:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_add(self, async_client: AsyncOpencode) -> None:
+ wellknown = await async_client.integration.wellknown.add(
+ url="url",
+ )
+ assert wellknown is None
+
+ @parametrize
+ async def test_method_add_with_all_params(self, async_client: AsyncOpencode) -> None:
+ wellknown = await async_client.integration.wellknown.add(
+ url="url",
+ location={
+ "directory": "string",
+ },
+ )
+ assert wellknown is None
+
+ @parametrize
+ async def test_raw_response_add(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.integration.wellknown.with_raw_response.add(
+ url="url",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ wellknown = await response.parse()
+ assert wellknown is None
+
+ @parametrize
+ async def test_streaming_response_add(self, async_client: AsyncOpencode) -> None:
+ async with async_client.integration.wellknown.with_streaming_response.add(
+ url="url",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ wellknown = await response.parse()
+ assert wellknown is None
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/mcp/__init__.py b/tests/api_resources/mcp/__init__.py
new file mode 100644
index 0000000..639750b
--- /dev/null
+++ b/tests/api_resources/mcp/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/mcp/test_resource.py b/tests/api_resources/mcp/test_resource.py
new file mode 100644
index 0000000..ec81a5c
--- /dev/null
+++ b/tests/api_resources/mcp/test_resource.py
@@ -0,0 +1,92 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.mcp import ResourceCatalogResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestResource:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_catalog(self, client: Opencode) -> None:
+ resource = client.mcp.resource.catalog()
+ assert_matches_type(ResourceCatalogResponse, resource, path=["response"])
+
+ @parametrize
+ def test_method_catalog_with_all_params(self, client: Opencode) -> None:
+ resource = client.mcp.resource.catalog(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ResourceCatalogResponse, resource, path=["response"])
+
+ @parametrize
+ def test_raw_response_catalog(self, client: Opencode) -> None:
+ response = client.mcp.resource.with_raw_response.catalog()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ resource = response.parse()
+ assert_matches_type(ResourceCatalogResponse, resource, path=["response"])
+
+ @parametrize
+ def test_streaming_response_catalog(self, client: Opencode) -> None:
+ with client.mcp.resource.with_streaming_response.catalog() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ resource = response.parse()
+ assert_matches_type(ResourceCatalogResponse, resource, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncResource:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_catalog(self, async_client: AsyncOpencode) -> None:
+ resource = await async_client.mcp.resource.catalog()
+ assert_matches_type(ResourceCatalogResponse, resource, path=["response"])
+
+ @parametrize
+ async def test_method_catalog_with_all_params(self, async_client: AsyncOpencode) -> None:
+ resource = await async_client.mcp.resource.catalog(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ResourceCatalogResponse, resource, path=["response"])
+
+ @parametrize
+ async def test_raw_response_catalog(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.mcp.resource.with_raw_response.catalog()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ resource = await response.parse()
+ assert_matches_type(ResourceCatalogResponse, resource, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_catalog(self, async_client: AsyncOpencode) -> None:
+ async with async_client.mcp.resource.with_streaming_response.catalog() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ resource = await response.parse()
+ assert_matches_type(ResourceCatalogResponse, resource, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/migration/__init__.py b/tests/api_resources/migration/__init__.py
new file mode 100644
index 0000000..639750b
--- /dev/null
+++ b/tests/api_resources/migration/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/migration/test_v1.py b/tests/api_resources/migration/test_v1.py
new file mode 100644
index 0000000..a2237f2
--- /dev/null
+++ b/tests/api_resources/migration/test_v1.py
@@ -0,0 +1,74 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.migration import V1StatusResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestV1:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_status(self, client: Opencode) -> None:
+ v1 = client.migration.v1.status()
+ assert_matches_type(V1StatusResponse, v1, path=["response"])
+
+ @parametrize
+ def test_raw_response_status(self, client: Opencode) -> None:
+ response = client.migration.v1.with_raw_response.status()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ v1 = response.parse()
+ assert_matches_type(V1StatusResponse, v1, path=["response"])
+
+ @parametrize
+ def test_streaming_response_status(self, client: Opencode) -> None:
+ with client.migration.v1.with_streaming_response.status() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ v1 = response.parse()
+ assert_matches_type(V1StatusResponse, v1, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncV1:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_status(self, async_client: AsyncOpencode) -> None:
+ v1 = await async_client.migration.v1.status()
+ assert_matches_type(V1StatusResponse, v1, path=["response"])
+
+ @parametrize
+ async def test_raw_response_status(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.migration.v1.with_raw_response.status()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ v1 = await response.parse()
+ assert_matches_type(V1StatusResponse, v1, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_status(self, async_client: AsyncOpencode) -> None:
+ async with async_client.migration.v1.with_streaming_response.status() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ v1 = await response.parse()
+ assert_matches_type(V1StatusResponse, v1, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/permission/__init__.py b/tests/api_resources/permission/__init__.py
new file mode 100644
index 0000000..639750b
--- /dev/null
+++ b/tests/api_resources/permission/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/permission/test_request.py b/tests/api_resources/permission/test_request.py
new file mode 100644
index 0000000..c788fc8
--- /dev/null
+++ b/tests/api_resources/permission/test_request.py
@@ -0,0 +1,92 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.permission import RequestListResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestRequest:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ request = client.permission.request.list()
+ assert_matches_type(RequestListResponse, request, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ request = client.permission.request.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(RequestListResponse, request, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.permission.request.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ request = response.parse()
+ assert_matches_type(RequestListResponse, request, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.permission.request.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ request = response.parse()
+ assert_matches_type(RequestListResponse, request, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncRequest:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ request = await async_client.permission.request.list()
+ assert_matches_type(RequestListResponse, request, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ request = await async_client.permission.request.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(RequestListResponse, request, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.permission.request.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ request = await response.parse()
+ assert_matches_type(RequestListResponse, request, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.permission.request.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ request = await response.parse()
+ assert_matches_type(RequestListResponse, request, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/permission/test_saved.py b/tests/api_resources/permission/test_saved.py
new file mode 100644
index 0000000..0374523
--- /dev/null
+++ b/tests/api_resources/permission/test_saved.py
@@ -0,0 +1,258 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.permission import SavedListResponse, SavedUpdateResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestSaved:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_update(self, client: Opencode) -> None:
+ saved = client.permission.saved.update(
+ id={},
+ )
+ assert_matches_type(SavedUpdateResponse, saved, path=["response"])
+
+ @parametrize
+ def test_method_update_with_all_params(self, client: Opencode) -> None:
+ saved = client.permission.saved.update(
+ id={},
+ action="action",
+ resource="resource",
+ )
+ assert_matches_type(SavedUpdateResponse, saved, path=["response"])
+
+ @parametrize
+ def test_raw_response_update(self, client: Opencode) -> None:
+ response = client.permission.saved.with_raw_response.update(
+ id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ saved = response.parse()
+ assert_matches_type(SavedUpdateResponse, saved, path=["response"])
+
+ @parametrize
+ def test_streaming_response_update(self, client: Opencode) -> None:
+ with client.permission.saved.with_streaming_response.update(
+ id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ saved = response.parse()
+ assert_matches_type(SavedUpdateResponse, saved, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_update(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.permission.saved.with_raw_response.update(
+ id="",
+ )
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ saved = client.permission.saved.list()
+ assert_matches_type(SavedListResponse, saved, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ saved = client.permission.saved.list(
+ project_id={},
+ )
+ assert_matches_type(SavedListResponse, saved, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.permission.saved.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ saved = response.parse()
+ assert_matches_type(SavedListResponse, saved, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.permission.saved.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ saved = response.parse()
+ assert_matches_type(SavedListResponse, saved, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_remove(self, client: Opencode) -> None:
+ saved = client.permission.saved.remove(
+ {},
+ )
+ assert saved is None
+
+ @parametrize
+ def test_raw_response_remove(self, client: Opencode) -> None:
+ response = client.permission.saved.with_raw_response.remove(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ saved = response.parse()
+ assert saved is None
+
+ @parametrize
+ def test_streaming_response_remove(self, client: Opencode) -> None:
+ with client.permission.saved.with_streaming_response.remove(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ saved = response.parse()
+ assert saved is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_remove(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.permission.saved.with_raw_response.remove(
+ "",
+ )
+
+
+class TestAsyncSaved:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_update(self, async_client: AsyncOpencode) -> None:
+ saved = await async_client.permission.saved.update(
+ id={},
+ )
+ assert_matches_type(SavedUpdateResponse, saved, path=["response"])
+
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncOpencode) -> None:
+ saved = await async_client.permission.saved.update(
+ id={},
+ action="action",
+ resource="resource",
+ )
+ assert_matches_type(SavedUpdateResponse, saved, path=["response"])
+
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.permission.saved.with_raw_response.update(
+ id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ saved = await response.parse()
+ assert_matches_type(SavedUpdateResponse, saved, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncOpencode) -> None:
+ async with async_client.permission.saved.with_streaming_response.update(
+ id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ saved = await response.parse()
+ assert_matches_type(SavedUpdateResponse, saved, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.permission.saved.with_raw_response.update(
+ id="",
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ saved = await async_client.permission.saved.list()
+ assert_matches_type(SavedListResponse, saved, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ saved = await async_client.permission.saved.list(
+ project_id={},
+ )
+ assert_matches_type(SavedListResponse, saved, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.permission.saved.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ saved = await response.parse()
+ assert_matches_type(SavedListResponse, saved, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.permission.saved.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ saved = await response.parse()
+ assert_matches_type(SavedListResponse, saved, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_remove(self, async_client: AsyncOpencode) -> None:
+ saved = await async_client.permission.saved.remove(
+ {},
+ )
+ assert saved is None
+
+ @parametrize
+ async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.permission.saved.with_raw_response.remove(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ saved = await response.parse()
+ assert saved is None
+
+ @parametrize
+ async def test_streaming_response_remove(self, async_client: AsyncOpencode) -> None:
+ async with async_client.permission.saved.with_streaming_response.remove(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ saved = await response.parse()
+ assert saved is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_remove(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.permission.saved.with_raw_response.remove(
+ "",
+ )
diff --git a/tests/api_resources/pty/__init__.py b/tests/api_resources/pty/__init__.py
new file mode 100644
index 0000000..639750b
--- /dev/null
+++ b/tests/api_resources/pty/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/pty/test_connect.py b/tests/api_resources/pty/test_connect.py
new file mode 100644
index 0000000..5ed18f6
--- /dev/null
+++ b/tests/api_resources/pty/test_connect.py
@@ -0,0 +1,122 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.pty import ConnectTokenResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestConnect:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_token(self, client: Opencode) -> None:
+ connect = client.pty.connect.token(
+ pty_id={},
+ )
+ assert_matches_type(ConnectTokenResponse, connect, path=["response"])
+
+ @parametrize
+ def test_method_token_with_all_params(self, client: Opencode) -> None:
+ connect = client.pty.connect.token(
+ pty_id={},
+ location={
+ "directory": "string",
+ },
+ x_opencode_ticket="x-opencode-ticket",
+ )
+ assert_matches_type(ConnectTokenResponse, connect, path=["response"])
+
+ @parametrize
+ def test_raw_response_token(self, client: Opencode) -> None:
+ response = client.pty.connect.with_raw_response.token(
+ pty_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ connect = response.parse()
+ assert_matches_type(ConnectTokenResponse, connect, path=["response"])
+
+ @parametrize
+ def test_streaming_response_token(self, client: Opencode) -> None:
+ with client.pty.connect.with_streaming_response.token(
+ pty_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ connect = response.parse()
+ assert_matches_type(ConnectTokenResponse, connect, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_token(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `pty_id` but received ''"):
+ client.pty.connect.with_raw_response.token(
+ pty_id="",
+ )
+
+
+class TestAsyncConnect:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_token(self, async_client: AsyncOpencode) -> None:
+ connect = await async_client.pty.connect.token(
+ pty_id={},
+ )
+ assert_matches_type(ConnectTokenResponse, connect, path=["response"])
+
+ @parametrize
+ async def test_method_token_with_all_params(self, async_client: AsyncOpencode) -> None:
+ connect = await async_client.pty.connect.token(
+ pty_id={},
+ location={
+ "directory": "string",
+ },
+ x_opencode_ticket="x-opencode-ticket",
+ )
+ assert_matches_type(ConnectTokenResponse, connect, path=["response"])
+
+ @parametrize
+ async def test_raw_response_token(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.pty.connect.with_raw_response.token(
+ pty_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ connect = await response.parse()
+ assert_matches_type(ConnectTokenResponse, connect, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_token(self, async_client: AsyncOpencode) -> None:
+ async with async_client.pty.connect.with_streaming_response.token(
+ pty_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ connect = await response.parse()
+ assert_matches_type(ConnectTokenResponse, connect, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_token(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `pty_id` but received ''"):
+ await async_client.pty.connect.with_raw_response.token(
+ pty_id="",
+ )
diff --git a/tests/api_resources/session/__init__.py b/tests/api_resources/session/__init__.py
new file mode 100644
index 0000000..639750b
--- /dev/null
+++ b/tests/api_resources/session/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/session/instructions/__init__.py b/tests/api_resources/session/instructions/__init__.py
new file mode 100644
index 0000000..639750b
--- /dev/null
+++ b/tests/api_resources/session/instructions/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/session/instructions/test_entry.py b/tests/api_resources/session/instructions/test_entry.py
new file mode 100644
index 0000000..e698ff3
--- /dev/null
+++ b/tests/api_resources/session/instructions/test_entry.py
@@ -0,0 +1,302 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.session.instructions import EntryListResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestEntry:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ entry = client.session.instructions.entry.list(
+ {},
+ )
+ assert_matches_type(EntryListResponse, entry, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.session.instructions.entry.with_raw_response.list(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ entry = response.parse()
+ assert_matches_type(EntryListResponse, entry, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.session.instructions.entry.with_streaming_response.list(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ entry = response.parse()
+ assert_matches_type(EntryListResponse, entry, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_list(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.instructions.entry.with_raw_response.list(
+ "",
+ )
+
+ @parametrize
+ def test_method_put(self, client: Opencode) -> None:
+ entry = client.session.instructions.entry.put(
+ key={},
+ session_id={},
+ value={},
+ )
+ assert entry is None
+
+ @parametrize
+ def test_raw_response_put(self, client: Opencode) -> None:
+ response = client.session.instructions.entry.with_raw_response.put(
+ key={},
+ session_id={},
+ value={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ entry = response.parse()
+ assert entry is None
+
+ @parametrize
+ def test_streaming_response_put(self, client: Opencode) -> None:
+ with client.session.instructions.entry.with_streaming_response.put(
+ key={},
+ session_id={},
+ value={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ entry = response.parse()
+ assert entry is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_put(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.instructions.entry.with_raw_response.put(
+ key={},
+ session_id="",
+ value={},
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.session.instructions.entry.with_raw_response.put(
+ key="",
+ session_id={},
+ value={},
+ )
+
+ @parametrize
+ def test_method_remove(self, client: Opencode) -> None:
+ entry = client.session.instructions.entry.remove(
+ key={},
+ session_id={},
+ )
+ assert entry is None
+
+ @parametrize
+ def test_raw_response_remove(self, client: Opencode) -> None:
+ response = client.session.instructions.entry.with_raw_response.remove(
+ key={},
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ entry = response.parse()
+ assert entry is None
+
+ @parametrize
+ def test_streaming_response_remove(self, client: Opencode) -> None:
+ with client.session.instructions.entry.with_streaming_response.remove(
+ key={},
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ entry = response.parse()
+ assert entry is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_remove(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.instructions.entry.with_raw_response.remove(
+ key={},
+ session_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ client.session.instructions.entry.with_raw_response.remove(
+ key="",
+ session_id={},
+ )
+
+
+class TestAsyncEntry:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ entry = await async_client.session.instructions.entry.list(
+ {},
+ )
+ assert_matches_type(EntryListResponse, entry, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.instructions.entry.with_raw_response.list(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ entry = await response.parse()
+ assert_matches_type(EntryListResponse, entry, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.instructions.entry.with_streaming_response.list(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ entry = await response.parse()
+ assert_matches_type(EntryListResponse, entry, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_list(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.instructions.entry.with_raw_response.list(
+ "",
+ )
+
+ @parametrize
+ async def test_method_put(self, async_client: AsyncOpencode) -> None:
+ entry = await async_client.session.instructions.entry.put(
+ key={},
+ session_id={},
+ value={},
+ )
+ assert entry is None
+
+ @parametrize
+ async def test_raw_response_put(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.instructions.entry.with_raw_response.put(
+ key={},
+ session_id={},
+ value={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ entry = await response.parse()
+ assert entry is None
+
+ @parametrize
+ async def test_streaming_response_put(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.instructions.entry.with_streaming_response.put(
+ key={},
+ session_id={},
+ value={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ entry = await response.parse()
+ assert entry is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_put(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.instructions.entry.with_raw_response.put(
+ key={},
+ session_id="",
+ value={},
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.session.instructions.entry.with_raw_response.put(
+ key="",
+ session_id={},
+ value={},
+ )
+
+ @parametrize
+ async def test_method_remove(self, async_client: AsyncOpencode) -> None:
+ entry = await async_client.session.instructions.entry.remove(
+ key={},
+ session_id={},
+ )
+ assert entry is None
+
+ @parametrize
+ async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.instructions.entry.with_raw_response.remove(
+ key={},
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ entry = await response.parse()
+ assert entry is None
+
+ @parametrize
+ async def test_streaming_response_remove(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.instructions.entry.with_streaming_response.remove(
+ key={},
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ entry = await response.parse()
+ assert entry is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_remove(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.instructions.entry.with_raw_response.remove(
+ key={},
+ session_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
+ await async_client.session.instructions.entry.with_raw_response.remove(
+ key="",
+ session_id={},
+ )
diff --git a/tests/api_resources/session/test_form.py b/tests/api_resources/session/test_form.py
new file mode 100644
index 0000000..6157939
--- /dev/null
+++ b/tests/api_resources/session/test_form.py
@@ -0,0 +1,530 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.session import FormGetResponse, FormListResponse, FormCreateResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestForm:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_create(self, client: Opencode) -> None:
+ form = client.session.form.create(
+ session_id="sessionID",
+ fields={},
+ title="title",
+ )
+ assert_matches_type(FormCreateResponse, form, path=["response"])
+
+ @parametrize
+ def test_method_create_with_all_params(self, client: Opencode) -> None:
+ form = client.session.form.create(
+ session_id="sessionID",
+ fields={},
+ title="title",
+ id={},
+ metadata={},
+ )
+ assert_matches_type(FormCreateResponse, form, path=["response"])
+
+ @parametrize
+ def test_raw_response_create(self, client: Opencode) -> None:
+ response = client.session.form.with_raw_response.create(
+ session_id="sessionID",
+ fields={},
+ title="title",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ form = response.parse()
+ assert_matches_type(FormCreateResponse, form, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create(self, client: Opencode) -> None:
+ with client.session.form.with_streaming_response.create(
+ session_id="sessionID",
+ fields={},
+ title="title",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ form = response.parse()
+ assert_matches_type(FormCreateResponse, form, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_create(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.form.with_raw_response.create(
+ session_id="",
+ fields={},
+ title="title",
+ )
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ form = client.session.form.list(
+ "sessionID",
+ )
+ assert_matches_type(FormListResponse, form, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.session.form.with_raw_response.list(
+ "sessionID",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ form = response.parse()
+ assert_matches_type(FormListResponse, form, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.session.form.with_streaming_response.list(
+ "sessionID",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ form = response.parse()
+ assert_matches_type(FormListResponse, form, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_list(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.form.with_raw_response.list(
+ "",
+ )
+
+ @parametrize
+ def test_method_cancel(self, client: Opencode) -> None:
+ form = client.session.form.cancel(
+ form_id={},
+ session_id="sessionID",
+ )
+ assert form is None
+
+ @parametrize
+ def test_method_cancel_with_all_params(self, client: Opencode) -> None:
+ form = client.session.form.cancel(
+ form_id={},
+ session_id="sessionID",
+ message="message",
+ )
+ assert form is None
+
+ @parametrize
+ def test_raw_response_cancel(self, client: Opencode) -> None:
+ response = client.session.form.with_raw_response.cancel(
+ form_id={},
+ session_id="sessionID",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ form = response.parse()
+ assert form is None
+
+ @parametrize
+ def test_streaming_response_cancel(self, client: Opencode) -> None:
+ with client.session.form.with_streaming_response.cancel(
+ form_id={},
+ session_id="sessionID",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ form = response.parse()
+ assert form is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_cancel(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.form.with_raw_response.cancel(
+ form_id={},
+ session_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `form_id` but received ''"):
+ client.session.form.with_raw_response.cancel(
+ form_id="",
+ session_id="sessionID",
+ )
+
+ @parametrize
+ def test_method_get(self, client: Opencode) -> None:
+ form = client.session.form.get(
+ form_id={},
+ session_id="sessionID",
+ )
+ assert_matches_type(FormGetResponse, form, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Opencode) -> None:
+ response = client.session.form.with_raw_response.get(
+ form_id={},
+ session_id="sessionID",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ form = response.parse()
+ assert_matches_type(FormGetResponse, form, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Opencode) -> None:
+ with client.session.form.with_streaming_response.get(
+ form_id={},
+ session_id="sessionID",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ form = response.parse()
+ assert_matches_type(FormGetResponse, form, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_get(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.form.with_raw_response.get(
+ form_id={},
+ session_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `form_id` but received ''"):
+ client.session.form.with_raw_response.get(
+ form_id="",
+ session_id="sessionID",
+ )
+
+ @parametrize
+ def test_method_reply(self, client: Opencode) -> None:
+ form = client.session.form.reply(
+ form_id={},
+ session_id="sessionID",
+ answer={},
+ )
+ assert form is None
+
+ @parametrize
+ def test_raw_response_reply(self, client: Opencode) -> None:
+ response = client.session.form.with_raw_response.reply(
+ form_id={},
+ session_id="sessionID",
+ answer={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ form = response.parse()
+ assert form is None
+
+ @parametrize
+ def test_streaming_response_reply(self, client: Opencode) -> None:
+ with client.session.form.with_streaming_response.reply(
+ form_id={},
+ session_id="sessionID",
+ answer={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ form = response.parse()
+ assert form is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_reply(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.form.with_raw_response.reply(
+ form_id={},
+ session_id="",
+ answer={},
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `form_id` but received ''"):
+ client.session.form.with_raw_response.reply(
+ form_id="",
+ session_id="sessionID",
+ answer={},
+ )
+
+
+class TestAsyncForm:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_create(self, async_client: AsyncOpencode) -> None:
+ form = await async_client.session.form.create(
+ session_id="sessionID",
+ fields={},
+ title="title",
+ )
+ assert_matches_type(FormCreateResponse, form, path=["response"])
+
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
+ form = await async_client.session.form.create(
+ session_id="sessionID",
+ fields={},
+ title="title",
+ id={},
+ metadata={},
+ )
+ assert_matches_type(FormCreateResponse, form, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.form.with_raw_response.create(
+ session_id="sessionID",
+ fields={},
+ title="title",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ form = await response.parse()
+ assert_matches_type(FormCreateResponse, form, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.form.with_streaming_response.create(
+ session_id="sessionID",
+ fields={},
+ title="title",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ form = await response.parse()
+ assert_matches_type(FormCreateResponse, form, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_create(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.form.with_raw_response.create(
+ session_id="",
+ fields={},
+ title="title",
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ form = await async_client.session.form.list(
+ "sessionID",
+ )
+ assert_matches_type(FormListResponse, form, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.form.with_raw_response.list(
+ "sessionID",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ form = await response.parse()
+ assert_matches_type(FormListResponse, form, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.form.with_streaming_response.list(
+ "sessionID",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ form = await response.parse()
+ assert_matches_type(FormListResponse, form, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_list(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.form.with_raw_response.list(
+ "",
+ )
+
+ @parametrize
+ async def test_method_cancel(self, async_client: AsyncOpencode) -> None:
+ form = await async_client.session.form.cancel(
+ form_id={},
+ session_id="sessionID",
+ )
+ assert form is None
+
+ @parametrize
+ async def test_method_cancel_with_all_params(self, async_client: AsyncOpencode) -> None:
+ form = await async_client.session.form.cancel(
+ form_id={},
+ session_id="sessionID",
+ message="message",
+ )
+ assert form is None
+
+ @parametrize
+ async def test_raw_response_cancel(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.form.with_raw_response.cancel(
+ form_id={},
+ session_id="sessionID",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ form = await response.parse()
+ assert form is None
+
+ @parametrize
+ async def test_streaming_response_cancel(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.form.with_streaming_response.cancel(
+ form_id={},
+ session_id="sessionID",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ form = await response.parse()
+ assert form is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_cancel(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.form.with_raw_response.cancel(
+ form_id={},
+ session_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `form_id` but received ''"):
+ await async_client.session.form.with_raw_response.cancel(
+ form_id="",
+ session_id="sessionID",
+ )
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncOpencode) -> None:
+ form = await async_client.session.form.get(
+ form_id={},
+ session_id="sessionID",
+ )
+ assert_matches_type(FormGetResponse, form, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.form.with_raw_response.get(
+ form_id={},
+ session_id="sessionID",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ form = await response.parse()
+ assert_matches_type(FormGetResponse, form, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.form.with_streaming_response.get(
+ form_id={},
+ session_id="sessionID",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ form = await response.parse()
+ assert_matches_type(FormGetResponse, form, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_get(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.form.with_raw_response.get(
+ form_id={},
+ session_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `form_id` but received ''"):
+ await async_client.session.form.with_raw_response.get(
+ form_id="",
+ session_id="sessionID",
+ )
+
+ @parametrize
+ async def test_method_reply(self, async_client: AsyncOpencode) -> None:
+ form = await async_client.session.form.reply(
+ form_id={},
+ session_id="sessionID",
+ answer={},
+ )
+ assert form is None
+
+ @parametrize
+ async def test_raw_response_reply(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.form.with_raw_response.reply(
+ form_id={},
+ session_id="sessionID",
+ answer={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ form = await response.parse()
+ assert form is None
+
+ @parametrize
+ async def test_streaming_response_reply(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.form.with_streaming_response.reply(
+ form_id={},
+ session_id="sessionID",
+ answer={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ form = await response.parse()
+ assert form is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_reply(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.form.with_raw_response.reply(
+ form_id={},
+ session_id="",
+ answer={},
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `form_id` but received ''"):
+ await async_client.session.form.with_raw_response.reply(
+ form_id="",
+ session_id="sessionID",
+ answer={},
+ )
diff --git a/tests/api_resources/session/test_inbox.py b/tests/api_resources/session/test_inbox.py
new file mode 100644
index 0000000..738071a
--- /dev/null
+++ b/tests/api_resources/session/test_inbox.py
@@ -0,0 +1,302 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.session import InboxListResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestInbox:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_update(self, client: Opencode) -> None:
+ inbox = client.session.inbox.update(
+ inbox_id={},
+ session_id={},
+ delivery={},
+ )
+ assert inbox is None
+
+ @parametrize
+ def test_raw_response_update(self, client: Opencode) -> None:
+ response = client.session.inbox.with_raw_response.update(
+ inbox_id={},
+ session_id={},
+ delivery={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ inbox = response.parse()
+ assert inbox is None
+
+ @parametrize
+ def test_streaming_response_update(self, client: Opencode) -> None:
+ with client.session.inbox.with_streaming_response.update(
+ inbox_id={},
+ session_id={},
+ delivery={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ inbox = response.parse()
+ assert inbox is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_update(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.inbox.with_raw_response.update(
+ inbox_id={},
+ session_id="",
+ delivery={},
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `inbox_id` but received ''"):
+ client.session.inbox.with_raw_response.update(
+ inbox_id="",
+ session_id={},
+ delivery={},
+ )
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ inbox = client.session.inbox.list(
+ {},
+ )
+ assert_matches_type(InboxListResponse, inbox, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.session.inbox.with_raw_response.list(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ inbox = response.parse()
+ assert_matches_type(InboxListResponse, inbox, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.session.inbox.with_streaming_response.list(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ inbox = response.parse()
+ assert_matches_type(InboxListResponse, inbox, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_list(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.inbox.with_raw_response.list(
+ "",
+ )
+
+ @parametrize
+ def test_method_cancel(self, client: Opencode) -> None:
+ inbox = client.session.inbox.cancel(
+ inbox_id={},
+ session_id={},
+ )
+ assert inbox is None
+
+ @parametrize
+ def test_raw_response_cancel(self, client: Opencode) -> None:
+ response = client.session.inbox.with_raw_response.cancel(
+ inbox_id={},
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ inbox = response.parse()
+ assert inbox is None
+
+ @parametrize
+ def test_streaming_response_cancel(self, client: Opencode) -> None:
+ with client.session.inbox.with_streaming_response.cancel(
+ inbox_id={},
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ inbox = response.parse()
+ assert inbox is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_cancel(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.inbox.with_raw_response.cancel(
+ inbox_id={},
+ session_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `inbox_id` but received ''"):
+ client.session.inbox.with_raw_response.cancel(
+ inbox_id="",
+ session_id={},
+ )
+
+
+class TestAsyncInbox:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_update(self, async_client: AsyncOpencode) -> None:
+ inbox = await async_client.session.inbox.update(
+ inbox_id={},
+ session_id={},
+ delivery={},
+ )
+ assert inbox is None
+
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.inbox.with_raw_response.update(
+ inbox_id={},
+ session_id={},
+ delivery={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ inbox = await response.parse()
+ assert inbox is None
+
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.inbox.with_streaming_response.update(
+ inbox_id={},
+ session_id={},
+ delivery={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ inbox = await response.parse()
+ assert inbox is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.inbox.with_raw_response.update(
+ inbox_id={},
+ session_id="",
+ delivery={},
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `inbox_id` but received ''"):
+ await async_client.session.inbox.with_raw_response.update(
+ inbox_id="",
+ session_id={},
+ delivery={},
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ inbox = await async_client.session.inbox.list(
+ {},
+ )
+ assert_matches_type(InboxListResponse, inbox, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.inbox.with_raw_response.list(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ inbox = await response.parse()
+ assert_matches_type(InboxListResponse, inbox, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.inbox.with_streaming_response.list(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ inbox = await response.parse()
+ assert_matches_type(InboxListResponse, inbox, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_list(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.inbox.with_raw_response.list(
+ "",
+ )
+
+ @parametrize
+ async def test_method_cancel(self, async_client: AsyncOpencode) -> None:
+ inbox = await async_client.session.inbox.cancel(
+ inbox_id={},
+ session_id={},
+ )
+ assert inbox is None
+
+ @parametrize
+ async def test_raw_response_cancel(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.inbox.with_raw_response.cancel(
+ inbox_id={},
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ inbox = await response.parse()
+ assert inbox is None
+
+ @parametrize
+ async def test_streaming_response_cancel(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.inbox.with_streaming_response.cancel(
+ inbox_id={},
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ inbox = await response.parse()
+ assert inbox is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_cancel(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.inbox.with_raw_response.cancel(
+ inbox_id={},
+ session_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `inbox_id` but received ''"):
+ await async_client.session.inbox.with_raw_response.cancel(
+ inbox_id="",
+ session_id={},
+ )
diff --git a/tests/api_resources/session/test_message.py b/tests/api_resources/session/test_message.py
new file mode 100644
index 0000000..a3efc16
--- /dev/null
+++ b/tests/api_resources/session/test_message.py
@@ -0,0 +1,120 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.session import MessageGetResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestMessage:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_get(self, client: Opencode) -> None:
+ message = client.session.message.get(
+ message_id={},
+ session_id={},
+ )
+ assert_matches_type(MessageGetResponse, message, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Opencode) -> None:
+ response = client.session.message.with_raw_response.get(
+ message_id={},
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ message = response.parse()
+ assert_matches_type(MessageGetResponse, message, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Opencode) -> None:
+ with client.session.message.with_streaming_response.get(
+ message_id={},
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ message = response.parse()
+ assert_matches_type(MessageGetResponse, message, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_get(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.message.with_raw_response.get(
+ message_id={},
+ session_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"):
+ client.session.message.with_raw_response.get(
+ message_id="",
+ session_id={},
+ )
+
+
+class TestAsyncMessage:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncOpencode) -> None:
+ message = await async_client.session.message.get(
+ message_id={},
+ session_id={},
+ )
+ assert_matches_type(MessageGetResponse, message, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.message.with_raw_response.get(
+ message_id={},
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ message = await response.parse()
+ assert_matches_type(MessageGetResponse, message, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.message.with_streaming_response.get(
+ message_id={},
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ message = await response.parse()
+ assert_matches_type(MessageGetResponse, message, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_get(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.message.with_raw_response.get(
+ message_id={},
+ session_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"):
+ await async_client.session.message.with_raw_response.get(
+ message_id="",
+ session_id={},
+ )
diff --git a/tests/api_resources/session/test_revert.py b/tests/api_resources/session/test_revert.py
new file mode 100644
index 0000000..74e5546
--- /dev/null
+++ b/tests/api_resources/session/test_revert.py
@@ -0,0 +1,278 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.session import RevertStageResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestRevert:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_clear(self, client: Opencode) -> None:
+ revert = client.session.revert.clear(
+ {},
+ )
+ assert revert is None
+
+ @parametrize
+ def test_raw_response_clear(self, client: Opencode) -> None:
+ response = client.session.revert.with_raw_response.clear(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ revert = response.parse()
+ assert revert is None
+
+ @parametrize
+ def test_streaming_response_clear(self, client: Opencode) -> None:
+ with client.session.revert.with_streaming_response.clear(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ revert = response.parse()
+ assert revert is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_clear(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.revert.with_raw_response.clear(
+ "",
+ )
+
+ @parametrize
+ def test_method_commit(self, client: Opencode) -> None:
+ revert = client.session.revert.commit(
+ {},
+ )
+ assert revert is None
+
+ @parametrize
+ def test_raw_response_commit(self, client: Opencode) -> None:
+ response = client.session.revert.with_raw_response.commit(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ revert = response.parse()
+ assert revert is None
+
+ @parametrize
+ def test_streaming_response_commit(self, client: Opencode) -> None:
+ with client.session.revert.with_streaming_response.commit(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ revert = response.parse()
+ assert revert is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_commit(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.revert.with_raw_response.commit(
+ "",
+ )
+
+ @parametrize
+ def test_method_stage(self, client: Opencode) -> None:
+ revert = client.session.revert.stage(
+ session_id={},
+ message_id={},
+ )
+ assert_matches_type(RevertStageResponse, revert, path=["response"])
+
+ @parametrize
+ def test_method_stage_with_all_params(self, client: Opencode) -> None:
+ revert = client.session.revert.stage(
+ session_id={},
+ message_id={},
+ files=True,
+ )
+ assert_matches_type(RevertStageResponse, revert, path=["response"])
+
+ @parametrize
+ def test_raw_response_stage(self, client: Opencode) -> None:
+ response = client.session.revert.with_raw_response.stage(
+ session_id={},
+ message_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ revert = response.parse()
+ assert_matches_type(RevertStageResponse, revert, path=["response"])
+
+ @parametrize
+ def test_streaming_response_stage(self, client: Opencode) -> None:
+ with client.session.revert.with_streaming_response.stage(
+ session_id={},
+ message_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ revert = response.parse()
+ assert_matches_type(RevertStageResponse, revert, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_stage(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.revert.with_raw_response.stage(
+ session_id="",
+ message_id={},
+ )
+
+
+class TestAsyncRevert:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_clear(self, async_client: AsyncOpencode) -> None:
+ revert = await async_client.session.revert.clear(
+ {},
+ )
+ assert revert is None
+
+ @parametrize
+ async def test_raw_response_clear(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.revert.with_raw_response.clear(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ revert = await response.parse()
+ assert revert is None
+
+ @parametrize
+ async def test_streaming_response_clear(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.revert.with_streaming_response.clear(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ revert = await response.parse()
+ assert revert is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_clear(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.revert.with_raw_response.clear(
+ "",
+ )
+
+ @parametrize
+ async def test_method_commit(self, async_client: AsyncOpencode) -> None:
+ revert = await async_client.session.revert.commit(
+ {},
+ )
+ assert revert is None
+
+ @parametrize
+ async def test_raw_response_commit(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.revert.with_raw_response.commit(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ revert = await response.parse()
+ assert revert is None
+
+ @parametrize
+ async def test_streaming_response_commit(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.revert.with_streaming_response.commit(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ revert = await response.parse()
+ assert revert is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_commit(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.revert.with_raw_response.commit(
+ "",
+ )
+
+ @parametrize
+ async def test_method_stage(self, async_client: AsyncOpencode) -> None:
+ revert = await async_client.session.revert.stage(
+ session_id={},
+ message_id={},
+ )
+ assert_matches_type(RevertStageResponse, revert, path=["response"])
+
+ @parametrize
+ async def test_method_stage_with_all_params(self, async_client: AsyncOpencode) -> None:
+ revert = await async_client.session.revert.stage(
+ session_id={},
+ message_id={},
+ files=True,
+ )
+ assert_matches_type(RevertStageResponse, revert, path=["response"])
+
+ @parametrize
+ async def test_raw_response_stage(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.revert.with_raw_response.stage(
+ session_id={},
+ message_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ revert = await response.parse()
+ assert_matches_type(RevertStageResponse, revert, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_stage(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.revert.with_streaming_response.stage(
+ session_id={},
+ message_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ revert = await response.parse()
+ assert_matches_type(RevertStageResponse, revert, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_stage(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.revert.with_raw_response.stage(
+ session_id="",
+ message_id={},
+ )
diff --git a/tests/api_resources/test_agent.py b/tests/api_resources/test_agent.py
new file mode 100644
index 0000000..d7b9a35
--- /dev/null
+++ b/tests/api_resources/test_agent.py
@@ -0,0 +1,188 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import AgentGetResponse, AgentListResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestAgent:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ agent = client.agent.list()
+ assert_matches_type(AgentListResponse, agent, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ agent = client.agent.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(AgentListResponse, agent, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.agent.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ agent = response.parse()
+ assert_matches_type(AgentListResponse, agent, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.agent.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ agent = response.parse()
+ assert_matches_type(AgentListResponse, agent, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_get(self, client: Opencode) -> None:
+ agent = client.agent.get(
+ agent_id={},
+ )
+ assert_matches_type(AgentGetResponse, agent, path=["response"])
+
+ @parametrize
+ def test_method_get_with_all_params(self, client: Opencode) -> None:
+ agent = client.agent.get(
+ agent_id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(AgentGetResponse, agent, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Opencode) -> None:
+ response = client.agent.with_raw_response.get(
+ agent_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ agent = response.parse()
+ assert_matches_type(AgentGetResponse, agent, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Opencode) -> None:
+ with client.agent.with_streaming_response.get(
+ agent_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ agent = response.parse()
+ assert_matches_type(AgentGetResponse, agent, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_get(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `agent_id` but received ''"):
+ client.agent.with_raw_response.get(
+ agent_id="",
+ )
+
+
+class TestAsyncAgent:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ agent = await async_client.agent.list()
+ assert_matches_type(AgentListResponse, agent, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ agent = await async_client.agent.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(AgentListResponse, agent, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.agent.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ agent = await response.parse()
+ assert_matches_type(AgentListResponse, agent, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.agent.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ agent = await response.parse()
+ assert_matches_type(AgentListResponse, agent, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncOpencode) -> None:
+ agent = await async_client.agent.get(
+ agent_id={},
+ )
+ assert_matches_type(AgentGetResponse, agent, path=["response"])
+
+ @parametrize
+ async def test_method_get_with_all_params(self, async_client: AsyncOpencode) -> None:
+ agent = await async_client.agent.get(
+ agent_id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(AgentGetResponse, agent, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.agent.with_raw_response.get(
+ agent_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ agent = await response.parse()
+ assert_matches_type(AgentGetResponse, agent, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
+ async with async_client.agent.with_streaming_response.get(
+ agent_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ agent = await response.parse()
+ assert_matches_type(AgentGetResponse, agent, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_get(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `agent_id` but received ''"):
+ await async_client.agent.with_raw_response.get(
+ agent_id="",
+ )
diff --git a/tests/api_resources/test_command.py b/tests/api_resources/test_command.py
new file mode 100644
index 0000000..add35c7
--- /dev/null
+++ b/tests/api_resources/test_command.py
@@ -0,0 +1,92 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import CommandListResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestCommand:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ command = client.command.list()
+ assert_matches_type(CommandListResponse, command, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ command = client.command.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(CommandListResponse, command, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.command.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ command = response.parse()
+ assert_matches_type(CommandListResponse, command, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.command.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ command = response.parse()
+ assert_matches_type(CommandListResponse, command, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncCommand:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ command = await async_client.command.list()
+ assert_matches_type(CommandListResponse, command, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ command = await async_client.command.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(CommandListResponse, command, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.command.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ command = await response.parse()
+ assert_matches_type(CommandListResponse, command, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.command.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ command = await response.parse()
+ assert_matches_type(CommandListResponse, command, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_config.py b/tests/api_resources/test_config.py
new file mode 100644
index 0000000..f9aa3e1
--- /dev/null
+++ b/tests/api_resources/test_config.py
@@ -0,0 +1,204 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import ConfigGetResponse, ConfigShellsResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestConfig:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_update(self, client: Opencode) -> None:
+ config = client.config.update(
+ shell="shell",
+ )
+ assert config is None
+
+ @parametrize
+ def test_raw_response_update(self, client: Opencode) -> None:
+ response = client.config.with_raw_response.update(
+ shell="shell",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ config = response.parse()
+ assert config is None
+
+ @parametrize
+ def test_streaming_response_update(self, client: Opencode) -> None:
+ with client.config.with_streaming_response.update(
+ shell="shell",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ config = response.parse()
+ assert config is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_get(self, client: Opencode) -> None:
+ config = client.config.get()
+ assert_matches_type(ConfigGetResponse, config, path=["response"])
+
+ @parametrize
+ def test_method_get_with_all_params(self, client: Opencode) -> None:
+ config = client.config.get(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ConfigGetResponse, config, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Opencode) -> None:
+ response = client.config.with_raw_response.get()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ config = response.parse()
+ assert_matches_type(ConfigGetResponse, config, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Opencode) -> None:
+ with client.config.with_streaming_response.get() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ config = response.parse()
+ assert_matches_type(ConfigGetResponse, config, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_shells(self, client: Opencode) -> None:
+ config = client.config.shells()
+ assert_matches_type(ConfigShellsResponse, config, path=["response"])
+
+ @parametrize
+ def test_raw_response_shells(self, client: Opencode) -> None:
+ response = client.config.with_raw_response.shells()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ config = response.parse()
+ assert_matches_type(ConfigShellsResponse, config, path=["response"])
+
+ @parametrize
+ def test_streaming_response_shells(self, client: Opencode) -> None:
+ with client.config.with_streaming_response.shells() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ config = response.parse()
+ assert_matches_type(ConfigShellsResponse, config, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncConfig:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_update(self, async_client: AsyncOpencode) -> None:
+ config = await async_client.config.update(
+ shell="shell",
+ )
+ assert config is None
+
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.config.with_raw_response.update(
+ shell="shell",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ config = await response.parse()
+ assert config is None
+
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncOpencode) -> None:
+ async with async_client.config.with_streaming_response.update(
+ shell="shell",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ config = await response.parse()
+ assert config is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncOpencode) -> None:
+ config = await async_client.config.get()
+ assert_matches_type(ConfigGetResponse, config, path=["response"])
+
+ @parametrize
+ async def test_method_get_with_all_params(self, async_client: AsyncOpencode) -> None:
+ config = await async_client.config.get(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ConfigGetResponse, config, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.config.with_raw_response.get()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ config = await response.parse()
+ assert_matches_type(ConfigGetResponse, config, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
+ async with async_client.config.with_streaming_response.get() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ config = await response.parse()
+ assert_matches_type(ConfigGetResponse, config, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_shells(self, async_client: AsyncOpencode) -> None:
+ config = await async_client.config.shells()
+ assert_matches_type(ConfigShellsResponse, config, path=["response"])
+
+ @parametrize
+ async def test_raw_response_shells(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.config.with_raw_response.shells()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ config = await response.parse()
+ assert_matches_type(ConfigShellsResponse, config, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_shells(self, async_client: AsyncOpencode) -> None:
+ async with async_client.config.with_streaming_response.shells() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ config = await response.parse()
+ assert_matches_type(ConfigShellsResponse, config, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_credential.py b/tests/api_resources/test_credential.py
new file mode 100644
index 0000000..3a27ecf
--- /dev/null
+++ b/tests/api_resources/test_credential.py
@@ -0,0 +1,400 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import CredentialListResponse, CredentialCreateResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestCredential:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_create(self, client: Opencode) -> None:
+ credential = client.credential.create(
+ integration_id={},
+ value={},
+ )
+ assert_matches_type(CredentialCreateResponse, credential, path=["response"])
+
+ @parametrize
+ def test_method_create_with_all_params(self, client: Opencode) -> None:
+ credential = client.credential.create(
+ integration_id={},
+ value={},
+ activate=True,
+ id={},
+ label="label",
+ )
+ assert_matches_type(CredentialCreateResponse, credential, path=["response"])
+
+ @parametrize
+ def test_raw_response_create(self, client: Opencode) -> None:
+ response = client.credential.with_raw_response.create(
+ integration_id={},
+ value={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ credential = response.parse()
+ assert_matches_type(CredentialCreateResponse, credential, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create(self, client: Opencode) -> None:
+ with client.credential.with_streaming_response.create(
+ integration_id={},
+ value={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ credential = response.parse()
+ assert_matches_type(CredentialCreateResponse, credential, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_update(self, client: Opencode) -> None:
+ credential = client.credential.update(
+ credential_id={},
+ label="label",
+ )
+ assert credential is None
+
+ @parametrize
+ def test_raw_response_update(self, client: Opencode) -> None:
+ response = client.credential.with_raw_response.update(
+ credential_id={},
+ label="label",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ credential = response.parse()
+ assert credential is None
+
+ @parametrize
+ def test_streaming_response_update(self, client: Opencode) -> None:
+ with client.credential.with_streaming_response.update(
+ credential_id={},
+ label="label",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ credential = response.parse()
+ assert credential is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_update(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `credential_id` but received ''"):
+ client.credential.with_raw_response.update(
+ credential_id="",
+ label="label",
+ )
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ credential = client.credential.list()
+ assert_matches_type(CredentialListResponse, credential, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.credential.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ credential = response.parse()
+ assert_matches_type(CredentialListResponse, credential, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.credential.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ credential = response.parse()
+ assert_matches_type(CredentialListResponse, credential, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_activate(self, client: Opencode) -> None:
+ credential = client.credential.activate(
+ {},
+ )
+ assert credential is None
+
+ @parametrize
+ def test_raw_response_activate(self, client: Opencode) -> None:
+ response = client.credential.with_raw_response.activate(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ credential = response.parse()
+ assert credential is None
+
+ @parametrize
+ def test_streaming_response_activate(self, client: Opencode) -> None:
+ with client.credential.with_streaming_response.activate(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ credential = response.parse()
+ assert credential is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_activate(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `credential_id` but received ''"):
+ client.credential.with_raw_response.activate(
+ "",
+ )
+
+ @parametrize
+ def test_method_remove(self, client: Opencode) -> None:
+ credential = client.credential.remove(
+ {},
+ )
+ assert credential is None
+
+ @parametrize
+ def test_raw_response_remove(self, client: Opencode) -> None:
+ response = client.credential.with_raw_response.remove(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ credential = response.parse()
+ assert credential is None
+
+ @parametrize
+ def test_streaming_response_remove(self, client: Opencode) -> None:
+ with client.credential.with_streaming_response.remove(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ credential = response.parse()
+ assert credential is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_remove(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `credential_id` but received ''"):
+ client.credential.with_raw_response.remove(
+ "",
+ )
+
+
+class TestAsyncCredential:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_create(self, async_client: AsyncOpencode) -> None:
+ credential = await async_client.credential.create(
+ integration_id={},
+ value={},
+ )
+ assert_matches_type(CredentialCreateResponse, credential, path=["response"])
+
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
+ credential = await async_client.credential.create(
+ integration_id={},
+ value={},
+ activate=True,
+ id={},
+ label="label",
+ )
+ assert_matches_type(CredentialCreateResponse, credential, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.credential.with_raw_response.create(
+ integration_id={},
+ value={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ credential = await response.parse()
+ assert_matches_type(CredentialCreateResponse, credential, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
+ async with async_client.credential.with_streaming_response.create(
+ integration_id={},
+ value={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ credential = await response.parse()
+ assert_matches_type(CredentialCreateResponse, credential, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_update(self, async_client: AsyncOpencode) -> None:
+ credential = await async_client.credential.update(
+ credential_id={},
+ label="label",
+ )
+ assert credential is None
+
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.credential.with_raw_response.update(
+ credential_id={},
+ label="label",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ credential = await response.parse()
+ assert credential is None
+
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncOpencode) -> None:
+ async with async_client.credential.with_streaming_response.update(
+ credential_id={},
+ label="label",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ credential = await response.parse()
+ assert credential is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `credential_id` but received ''"):
+ await async_client.credential.with_raw_response.update(
+ credential_id="",
+ label="label",
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ credential = await async_client.credential.list()
+ assert_matches_type(CredentialListResponse, credential, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.credential.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ credential = await response.parse()
+ assert_matches_type(CredentialListResponse, credential, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.credential.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ credential = await response.parse()
+ assert_matches_type(CredentialListResponse, credential, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_activate(self, async_client: AsyncOpencode) -> None:
+ credential = await async_client.credential.activate(
+ {},
+ )
+ assert credential is None
+
+ @parametrize
+ async def test_raw_response_activate(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.credential.with_raw_response.activate(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ credential = await response.parse()
+ assert credential is None
+
+ @parametrize
+ async def test_streaming_response_activate(self, async_client: AsyncOpencode) -> None:
+ async with async_client.credential.with_streaming_response.activate(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ credential = await response.parse()
+ assert credential is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_activate(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `credential_id` but received ''"):
+ await async_client.credential.with_raw_response.activate(
+ "",
+ )
+
+ @parametrize
+ async def test_method_remove(self, async_client: AsyncOpencode) -> None:
+ credential = await async_client.credential.remove(
+ {},
+ )
+ assert credential is None
+
+ @parametrize
+ async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.credential.with_raw_response.remove(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ credential = await response.parse()
+ assert credential is None
+
+ @parametrize
+ async def test_streaming_response_remove(self, async_client: AsyncOpencode) -> None:
+ async with async_client.credential.with_streaming_response.remove(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ credential = await response.parse()
+ assert credential is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_remove(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `credential_id` but received ''"):
+ await async_client.credential.with_raw_response.remove(
+ "",
+ )
diff --git a/tests/api_resources/test_event.py b/tests/api_resources/test_event.py
new file mode 100644
index 0000000..1a9595d
--- /dev/null
+++ b/tests/api_resources/test_event.py
@@ -0,0 +1,74 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.shared import Event
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestEvent:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_subscribe(self, client: Opencode) -> None:
+ event = client.event.subscribe()
+ assert_matches_type(Event, event, path=["response"])
+
+ @parametrize
+ def test_raw_response_subscribe(self, client: Opencode) -> None:
+ response = client.event.with_raw_response.subscribe()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ event = response.parse()
+ assert_matches_type(Event, event, path=["response"])
+
+ @parametrize
+ def test_streaming_response_subscribe(self, client: Opencode) -> None:
+ with client.event.with_streaming_response.subscribe() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ event = response.parse()
+ assert_matches_type(Event, event, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncEvent:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_subscribe(self, async_client: AsyncOpencode) -> None:
+ event = await async_client.event.subscribe()
+ assert_matches_type(Event, event, path=["response"])
+
+ @parametrize
+ async def test_raw_response_subscribe(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.event.with_raw_response.subscribe()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ event = await response.parse()
+ assert_matches_type(Event, event, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_subscribe(self, async_client: AsyncOpencode) -> None:
+ async with async_client.event.with_streaming_response.subscribe() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ event = await response.parse()
+ assert_matches_type(Event, event, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_file.py b/tests/api_resources/test_file.py
new file mode 100644
index 0000000..46f5b19
--- /dev/null
+++ b/tests/api_resources/test_file.py
@@ -0,0 +1,366 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import FileFindResponse, FileListResponse, FileWriteResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestFile:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ file = client.file.list()
+ assert_matches_type(FileListResponse, file, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ file = client.file.list(
+ location={
+ "directory": "string",
+ },
+ path="path",
+ )
+ assert_matches_type(FileListResponse, file, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.file.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ file = response.parse()
+ assert_matches_type(FileListResponse, file, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.file.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ file = response.parse()
+ assert_matches_type(FileListResponse, file, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_find(self, client: Opencode) -> None:
+ file = client.file.find(
+ query="query",
+ )
+ assert_matches_type(FileFindResponse, file, path=["response"])
+
+ @parametrize
+ def test_method_find_with_all_params(self, client: Opencode) -> None:
+ file = client.file.find(
+ query="query",
+ limit=0,
+ location={
+ "directory": "string",
+ },
+ type="file",
+ )
+ assert_matches_type(FileFindResponse, file, path=["response"])
+
+ @parametrize
+ def test_raw_response_find(self, client: Opencode) -> None:
+ response = client.file.with_raw_response.find(
+ query="query",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ file = response.parse()
+ assert_matches_type(FileFindResponse, file, path=["response"])
+
+ @parametrize
+ def test_streaming_response_find(self, client: Opencode) -> None:
+ with client.file.with_streaming_response.find(
+ query="query",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ file = response.parse()
+ assert_matches_type(FileFindResponse, file, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_read(self, client: Opencode) -> None:
+ file = client.file.read(
+ path="path",
+ )
+ assert file is None
+
+ @parametrize
+ def test_method_read_with_all_params(self, client: Opencode) -> None:
+ file = client.file.read(
+ path="path",
+ location={
+ "directory": "string",
+ },
+ )
+ assert file is None
+
+ @parametrize
+ def test_raw_response_read(self, client: Opencode) -> None:
+ response = client.file.with_raw_response.read(
+ path="path",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ file = response.parse()
+ assert file is None
+
+ @parametrize
+ def test_streaming_response_read(self, client: Opencode) -> None:
+ with client.file.with_streaming_response.read(
+ path="path",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ file = response.parse()
+ assert file is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_read(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `path` but received ''"):
+ client.file.with_raw_response.read(
+ path="",
+ )
+
+ @parametrize
+ def test_method_write(self, client: Opencode) -> None:
+ file = client.file.write(
+ body=b"raw file contents",
+ path="path",
+ )
+ assert_matches_type(FileWriteResponse, file, path=["response"])
+
+ @parametrize
+ def test_method_write_with_all_params(self, client: Opencode) -> None:
+ file = client.file.write(
+ body=b"raw file contents",
+ path="path",
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(FileWriteResponse, file, path=["response"])
+
+ @parametrize
+ def test_raw_response_write(self, client: Opencode) -> None:
+ response = client.file.with_raw_response.write(
+ body=b"raw file contents",
+ path="path",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ file = response.parse()
+ assert_matches_type(FileWriteResponse, file, path=["response"])
+
+ @parametrize
+ def test_streaming_response_write(self, client: Opencode) -> None:
+ with client.file.with_streaming_response.write(
+ body=b"raw file contents",
+ path="path",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ file = response.parse()
+ assert_matches_type(FileWriteResponse, file, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncFile:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ file = await async_client.file.list()
+ assert_matches_type(FileListResponse, file, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ file = await async_client.file.list(
+ location={
+ "directory": "string",
+ },
+ path="path",
+ )
+ assert_matches_type(FileListResponse, file, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.file.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ file = await response.parse()
+ assert_matches_type(FileListResponse, file, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.file.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ file = await response.parse()
+ assert_matches_type(FileListResponse, file, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_find(self, async_client: AsyncOpencode) -> None:
+ file = await async_client.file.find(
+ query="query",
+ )
+ assert_matches_type(FileFindResponse, file, path=["response"])
+
+ @parametrize
+ async def test_method_find_with_all_params(self, async_client: AsyncOpencode) -> None:
+ file = await async_client.file.find(
+ query="query",
+ limit=0,
+ location={
+ "directory": "string",
+ },
+ type="file",
+ )
+ assert_matches_type(FileFindResponse, file, path=["response"])
+
+ @parametrize
+ async def test_raw_response_find(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.file.with_raw_response.find(
+ query="query",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ file = await response.parse()
+ assert_matches_type(FileFindResponse, file, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_find(self, async_client: AsyncOpencode) -> None:
+ async with async_client.file.with_streaming_response.find(
+ query="query",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ file = await response.parse()
+ assert_matches_type(FileFindResponse, file, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_read(self, async_client: AsyncOpencode) -> None:
+ file = await async_client.file.read(
+ path="path",
+ )
+ assert file is None
+
+ @parametrize
+ async def test_method_read_with_all_params(self, async_client: AsyncOpencode) -> None:
+ file = await async_client.file.read(
+ path="path",
+ location={
+ "directory": "string",
+ },
+ )
+ assert file is None
+
+ @parametrize
+ async def test_raw_response_read(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.file.with_raw_response.read(
+ path="path",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ file = await response.parse()
+ assert file is None
+
+ @parametrize
+ async def test_streaming_response_read(self, async_client: AsyncOpencode) -> None:
+ async with async_client.file.with_streaming_response.read(
+ path="path",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ file = await response.parse()
+ assert file is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_read(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `path` but received ''"):
+ await async_client.file.with_raw_response.read(
+ path="",
+ )
+
+ @parametrize
+ async def test_method_write(self, async_client: AsyncOpencode) -> None:
+ file = await async_client.file.write(
+ body=b"raw file contents",
+ path="path",
+ )
+ assert_matches_type(FileWriteResponse, file, path=["response"])
+
+ @parametrize
+ async def test_method_write_with_all_params(self, async_client: AsyncOpencode) -> None:
+ file = await async_client.file.write(
+ body=b"raw file contents",
+ path="path",
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(FileWriteResponse, file, path=["response"])
+
+ @parametrize
+ async def test_raw_response_write(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.file.with_raw_response.write(
+ body=b"raw file contents",
+ path="path",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ file = await response.parse()
+ assert_matches_type(FileWriteResponse, file, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_write(self, async_client: AsyncOpencode) -> None:
+ async with async_client.file.with_streaming_response.write(
+ body=b"raw file contents",
+ path="path",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ file = await response.parse()
+ assert_matches_type(FileWriteResponse, file, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_form.py b/tests/api_resources/test_form.py
new file mode 100644
index 0000000..ba7d7ac
--- /dev/null
+++ b/tests/api_resources/test_form.py
@@ -0,0 +1,92 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import FormListResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestForm:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ form = client.form.list()
+ assert_matches_type(FormListResponse, form, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ form = client.form.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(FormListResponse, form, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.form.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ form = response.parse()
+ assert_matches_type(FormListResponse, form, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.form.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ form = response.parse()
+ assert_matches_type(FormListResponse, form, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncForm:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ form = await async_client.form.list()
+ assert_matches_type(FormListResponse, form, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ form = await async_client.form.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(FormListResponse, form, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.form.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ form = await response.parse()
+ assert_matches_type(FormListResponse, form, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.form.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ form = await response.parse()
+ assert_matches_type(FormListResponse, form, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_generate.py b/tests/api_resources/test_generate.py
new file mode 100644
index 0000000..86874db
--- /dev/null
+++ b/tests/api_resources/test_generate.py
@@ -0,0 +1,102 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.shared import GenerateTextResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestGenerate:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_text(self, client: Opencode) -> None:
+ generate = client.generate.text(
+ prompt="prompt",
+ )
+ assert_matches_type(GenerateTextResponse, generate, path=["response"])
+
+ @parametrize
+ def test_method_text_with_all_params(self, client: Opencode) -> None:
+ generate = client.generate.text(
+ prompt="prompt",
+ model={},
+ )
+ assert_matches_type(GenerateTextResponse, generate, path=["response"])
+
+ @parametrize
+ def test_raw_response_text(self, client: Opencode) -> None:
+ response = client.generate.with_raw_response.text(
+ prompt="prompt",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ generate = response.parse()
+ assert_matches_type(GenerateTextResponse, generate, path=["response"])
+
+ @parametrize
+ def test_streaming_response_text(self, client: Opencode) -> None:
+ with client.generate.with_streaming_response.text(
+ prompt="prompt",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ generate = response.parse()
+ assert_matches_type(GenerateTextResponse, generate, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncGenerate:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_text(self, async_client: AsyncOpencode) -> None:
+ generate = await async_client.generate.text(
+ prompt="prompt",
+ )
+ assert_matches_type(GenerateTextResponse, generate, path=["response"])
+
+ @parametrize
+ async def test_method_text_with_all_params(self, async_client: AsyncOpencode) -> None:
+ generate = await async_client.generate.text(
+ prompt="prompt",
+ model={},
+ )
+ assert_matches_type(GenerateTextResponse, generate, path=["response"])
+
+ @parametrize
+ async def test_raw_response_text(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.generate.with_raw_response.text(
+ prompt="prompt",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ generate = await response.parse()
+ assert_matches_type(GenerateTextResponse, generate, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_text(self, async_client: AsyncOpencode) -> None:
+ async with async_client.generate.with_streaming_response.text(
+ prompt="prompt",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ generate = await response.parse()
+ assert_matches_type(GenerateTextResponse, generate, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_integration.py b/tests/api_resources/test_integration.py
new file mode 100644
index 0000000..59ed989
--- /dev/null
+++ b/tests/api_resources/test_integration.py
@@ -0,0 +1,188 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import IntegrationGetResponse, IntegrationListResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestIntegration:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ integration = client.integration.list()
+ assert_matches_type(IntegrationListResponse, integration, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ integration = client.integration.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(IntegrationListResponse, integration, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.integration.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ integration = response.parse()
+ assert_matches_type(IntegrationListResponse, integration, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.integration.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ integration = response.parse()
+ assert_matches_type(IntegrationListResponse, integration, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_get(self, client: Opencode) -> None:
+ integration = client.integration.get(
+ integration_id={},
+ )
+ assert_matches_type(IntegrationGetResponse, integration, path=["response"])
+
+ @parametrize
+ def test_method_get_with_all_params(self, client: Opencode) -> None:
+ integration = client.integration.get(
+ integration_id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(IntegrationGetResponse, integration, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Opencode) -> None:
+ response = client.integration.with_raw_response.get(
+ integration_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ integration = response.parse()
+ assert_matches_type(IntegrationGetResponse, integration, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Opencode) -> None:
+ with client.integration.with_streaming_response.get(
+ integration_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ integration = response.parse()
+ assert_matches_type(IntegrationGetResponse, integration, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_get(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ client.integration.with_raw_response.get(
+ integration_id="",
+ )
+
+
+class TestAsyncIntegration:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ integration = await async_client.integration.list()
+ assert_matches_type(IntegrationListResponse, integration, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ integration = await async_client.integration.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(IntegrationListResponse, integration, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.integration.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ integration = await response.parse()
+ assert_matches_type(IntegrationListResponse, integration, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.integration.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ integration = await response.parse()
+ assert_matches_type(IntegrationListResponse, integration, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncOpencode) -> None:
+ integration = await async_client.integration.get(
+ integration_id={},
+ )
+ assert_matches_type(IntegrationGetResponse, integration, path=["response"])
+
+ @parametrize
+ async def test_method_get_with_all_params(self, async_client: AsyncOpencode) -> None:
+ integration = await async_client.integration.get(
+ integration_id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(IntegrationGetResponse, integration, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.integration.with_raw_response.get(
+ integration_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ integration = await response.parse()
+ assert_matches_type(IntegrationGetResponse, integration, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
+ async with async_client.integration.with_streaming_response.get(
+ integration_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ integration = await response.parse()
+ assert_matches_type(IntegrationGetResponse, integration, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_get(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
+ await async_client.integration.with_raw_response.get(
+ integration_id="",
+ )
diff --git a/tests/api_resources/test_location.py b/tests/api_resources/test_location.py
new file mode 100644
index 0000000..da4c890
--- /dev/null
+++ b/tests/api_resources/test_location.py
@@ -0,0 +1,142 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.shared import LocationPublicInfo
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestLocation:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_get(self, client: Opencode) -> None:
+ location = client.location.get()
+ assert_matches_type(LocationPublicInfo, location, path=["response"])
+
+ @parametrize
+ def test_method_get_with_all_params(self, client: Opencode) -> None:
+ location = client.location.get(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(LocationPublicInfo, location, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Opencode) -> None:
+ response = client.location.with_raw_response.get()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ location = response.parse()
+ assert_matches_type(LocationPublicInfo, location, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Opencode) -> None:
+ with client.location.with_streaming_response.get() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ location = response.parse()
+ assert_matches_type(LocationPublicInfo, location, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_reload(self, client: Opencode) -> None:
+ location = client.location.reload()
+ assert location is None
+
+ @parametrize
+ def test_raw_response_reload(self, client: Opencode) -> None:
+ response = client.location.with_raw_response.reload()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ location = response.parse()
+ assert location is None
+
+ @parametrize
+ def test_streaming_response_reload(self, client: Opencode) -> None:
+ with client.location.with_streaming_response.reload() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ location = response.parse()
+ assert location is None
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncLocation:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncOpencode) -> None:
+ location = await async_client.location.get()
+ assert_matches_type(LocationPublicInfo, location, path=["response"])
+
+ @parametrize
+ async def test_method_get_with_all_params(self, async_client: AsyncOpencode) -> None:
+ location = await async_client.location.get(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(LocationPublicInfo, location, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.location.with_raw_response.get()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ location = await response.parse()
+ assert_matches_type(LocationPublicInfo, location, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
+ async with async_client.location.with_streaming_response.get() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ location = await response.parse()
+ assert_matches_type(LocationPublicInfo, location, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_reload(self, async_client: AsyncOpencode) -> None:
+ location = await async_client.location.reload()
+ assert location is None
+
+ @parametrize
+ async def test_raw_response_reload(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.location.with_raw_response.reload()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ location = await response.parse()
+ assert location is None
+
+ @parametrize
+ async def test_streaming_response_reload(self, async_client: AsyncOpencode) -> None:
+ async with async_client.location.with_streaming_response.reload() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ location = await response.parse()
+ assert location is None
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_mcp.py b/tests/api_resources/test_mcp.py
new file mode 100644
index 0000000..2a85eef
--- /dev/null
+++ b/tests/api_resources/test_mcp.py
@@ -0,0 +1,486 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import McpListResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestMcp:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ mcp = client.mcp.list()
+ assert_matches_type(McpListResponse, mcp, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ mcp = client.mcp.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(McpListResponse, mcp, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.mcp.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ mcp = response.parse()
+ assert_matches_type(McpListResponse, mcp, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.mcp.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ mcp = response.parse()
+ assert_matches_type(McpListResponse, mcp, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_add(self, client: Opencode) -> None:
+ mcp = client.mcp.add(
+ server="server",
+ config={},
+ )
+ assert mcp is None
+
+ @parametrize
+ def test_method_add_with_all_params(self, client: Opencode) -> None:
+ mcp = client.mcp.add(
+ server="server",
+ config={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert mcp is None
+
+ @parametrize
+ def test_raw_response_add(self, client: Opencode) -> None:
+ response = client.mcp.with_raw_response.add(
+ server="server",
+ config={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ mcp = response.parse()
+ assert mcp is None
+
+ @parametrize
+ def test_streaming_response_add(self, client: Opencode) -> None:
+ with client.mcp.with_streaming_response.add(
+ server="server",
+ config={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ mcp = response.parse()
+ assert mcp is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_add(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `server` but received ''"):
+ client.mcp.with_raw_response.add(
+ server="",
+ config={},
+ )
+
+ @parametrize
+ def test_method_connect(self, client: Opencode) -> None:
+ mcp = client.mcp.connect(
+ server="server",
+ )
+ assert mcp is None
+
+ @parametrize
+ def test_method_connect_with_all_params(self, client: Opencode) -> None:
+ mcp = client.mcp.connect(
+ server="server",
+ location={
+ "directory": "string",
+ },
+ )
+ assert mcp is None
+
+ @parametrize
+ def test_raw_response_connect(self, client: Opencode) -> None:
+ response = client.mcp.with_raw_response.connect(
+ server="server",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ mcp = response.parse()
+ assert mcp is None
+
+ @parametrize
+ def test_streaming_response_connect(self, client: Opencode) -> None:
+ with client.mcp.with_streaming_response.connect(
+ server="server",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ mcp = response.parse()
+ assert mcp is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_connect(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `server` but received ''"):
+ client.mcp.with_raw_response.connect(
+ server="",
+ )
+
+ @parametrize
+ def test_method_disconnect(self, client: Opencode) -> None:
+ mcp = client.mcp.disconnect(
+ server="server",
+ )
+ assert mcp is None
+
+ @parametrize
+ def test_method_disconnect_with_all_params(self, client: Opencode) -> None:
+ mcp = client.mcp.disconnect(
+ server="server",
+ location={
+ "directory": "string",
+ },
+ )
+ assert mcp is None
+
+ @parametrize
+ def test_raw_response_disconnect(self, client: Opencode) -> None:
+ response = client.mcp.with_raw_response.disconnect(
+ server="server",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ mcp = response.parse()
+ assert mcp is None
+
+ @parametrize
+ def test_streaming_response_disconnect(self, client: Opencode) -> None:
+ with client.mcp.with_streaming_response.disconnect(
+ server="server",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ mcp = response.parse()
+ assert mcp is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_disconnect(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `server` but received ''"):
+ client.mcp.with_raw_response.disconnect(
+ server="",
+ )
+
+ @parametrize
+ def test_method_remove(self, client: Opencode) -> None:
+ mcp = client.mcp.remove(
+ server="server",
+ )
+ assert mcp is None
+
+ @parametrize
+ def test_method_remove_with_all_params(self, client: Opencode) -> None:
+ mcp = client.mcp.remove(
+ server="server",
+ location={
+ "directory": "string",
+ },
+ )
+ assert mcp is None
+
+ @parametrize
+ def test_raw_response_remove(self, client: Opencode) -> None:
+ response = client.mcp.with_raw_response.remove(
+ server="server",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ mcp = response.parse()
+ assert mcp is None
+
+ @parametrize
+ def test_streaming_response_remove(self, client: Opencode) -> None:
+ with client.mcp.with_streaming_response.remove(
+ server="server",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ mcp = response.parse()
+ assert mcp is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_remove(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `server` but received ''"):
+ client.mcp.with_raw_response.remove(
+ server="",
+ )
+
+
+class TestAsyncMcp:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ mcp = await async_client.mcp.list()
+ assert_matches_type(McpListResponse, mcp, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ mcp = await async_client.mcp.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(McpListResponse, mcp, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.mcp.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ mcp = await response.parse()
+ assert_matches_type(McpListResponse, mcp, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.mcp.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ mcp = await response.parse()
+ assert_matches_type(McpListResponse, mcp, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_add(self, async_client: AsyncOpencode) -> None:
+ mcp = await async_client.mcp.add(
+ server="server",
+ config={},
+ )
+ assert mcp is None
+
+ @parametrize
+ async def test_method_add_with_all_params(self, async_client: AsyncOpencode) -> None:
+ mcp = await async_client.mcp.add(
+ server="server",
+ config={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert mcp is None
+
+ @parametrize
+ async def test_raw_response_add(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.mcp.with_raw_response.add(
+ server="server",
+ config={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ mcp = await response.parse()
+ assert mcp is None
+
+ @parametrize
+ async def test_streaming_response_add(self, async_client: AsyncOpencode) -> None:
+ async with async_client.mcp.with_streaming_response.add(
+ server="server",
+ config={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ mcp = await response.parse()
+ assert mcp is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_add(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `server` but received ''"):
+ await async_client.mcp.with_raw_response.add(
+ server="",
+ config={},
+ )
+
+ @parametrize
+ async def test_method_connect(self, async_client: AsyncOpencode) -> None:
+ mcp = await async_client.mcp.connect(
+ server="server",
+ )
+ assert mcp is None
+
+ @parametrize
+ async def test_method_connect_with_all_params(self, async_client: AsyncOpencode) -> None:
+ mcp = await async_client.mcp.connect(
+ server="server",
+ location={
+ "directory": "string",
+ },
+ )
+ assert mcp is None
+
+ @parametrize
+ async def test_raw_response_connect(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.mcp.with_raw_response.connect(
+ server="server",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ mcp = await response.parse()
+ assert mcp is None
+
+ @parametrize
+ async def test_streaming_response_connect(self, async_client: AsyncOpencode) -> None:
+ async with async_client.mcp.with_streaming_response.connect(
+ server="server",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ mcp = await response.parse()
+ assert mcp is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_connect(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `server` but received ''"):
+ await async_client.mcp.with_raw_response.connect(
+ server="",
+ )
+
+ @parametrize
+ async def test_method_disconnect(self, async_client: AsyncOpencode) -> None:
+ mcp = await async_client.mcp.disconnect(
+ server="server",
+ )
+ assert mcp is None
+
+ @parametrize
+ async def test_method_disconnect_with_all_params(self, async_client: AsyncOpencode) -> None:
+ mcp = await async_client.mcp.disconnect(
+ server="server",
+ location={
+ "directory": "string",
+ },
+ )
+ assert mcp is None
+
+ @parametrize
+ async def test_raw_response_disconnect(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.mcp.with_raw_response.disconnect(
+ server="server",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ mcp = await response.parse()
+ assert mcp is None
+
+ @parametrize
+ async def test_streaming_response_disconnect(self, async_client: AsyncOpencode) -> None:
+ async with async_client.mcp.with_streaming_response.disconnect(
+ server="server",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ mcp = await response.parse()
+ assert mcp is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_disconnect(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `server` but received ''"):
+ await async_client.mcp.with_raw_response.disconnect(
+ server="",
+ )
+
+ @parametrize
+ async def test_method_remove(self, async_client: AsyncOpencode) -> None:
+ mcp = await async_client.mcp.remove(
+ server="server",
+ )
+ assert mcp is None
+
+ @parametrize
+ async def test_method_remove_with_all_params(self, async_client: AsyncOpencode) -> None:
+ mcp = await async_client.mcp.remove(
+ server="server",
+ location={
+ "directory": "string",
+ },
+ )
+ assert mcp is None
+
+ @parametrize
+ async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.mcp.with_raw_response.remove(
+ server="server",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ mcp = await response.parse()
+ assert mcp is None
+
+ @parametrize
+ async def test_streaming_response_remove(self, async_client: AsyncOpencode) -> None:
+ async with async_client.mcp.with_streaming_response.remove(
+ server="server",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ mcp = await response.parse()
+ assert mcp is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_remove(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `server` but received ''"):
+ await async_client.mcp.with_raw_response.remove(
+ server="",
+ )
diff --git a/tests/api_resources/test_message.py b/tests/api_resources/test_message.py
new file mode 100644
index 0000000..ae9d857
--- /dev/null
+++ b/tests/api_resources/test_message.py
@@ -0,0 +1,122 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.shared import SessionMessageInfo
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestMessage:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ message = client.message.list(
+ session_id={},
+ )
+ assert_matches_type(SessionMessageInfo, message, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ message = client.message.list(
+ session_id={},
+ cursor="cursor",
+ limit=0,
+ order="asc",
+ type="agent-switched",
+ )
+ assert_matches_type(SessionMessageInfo, message, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.message.with_raw_response.list(
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ message = response.parse()
+ assert_matches_type(SessionMessageInfo, message, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.message.with_streaming_response.list(
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ message = response.parse()
+ assert_matches_type(SessionMessageInfo, message, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_list(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.message.with_raw_response.list(
+ session_id="",
+ )
+
+
+class TestAsyncMessage:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ message = await async_client.message.list(
+ session_id={},
+ )
+ assert_matches_type(SessionMessageInfo, message, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ message = await async_client.message.list(
+ session_id={},
+ cursor="cursor",
+ limit=0,
+ order="asc",
+ type="agent-switched",
+ )
+ assert_matches_type(SessionMessageInfo, message, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.message.with_raw_response.list(
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ message = await response.parse()
+ assert_matches_type(SessionMessageInfo, message, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.message.with_streaming_response.list(
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ message = await response.parse()
+ assert_matches_type(SessionMessageInfo, message, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_list(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.message.with_raw_response.list(
+ session_id="",
+ )
diff --git a/tests/api_resources/test_model.py b/tests/api_resources/test_model.py
new file mode 100644
index 0000000..8d15549
--- /dev/null
+++ b/tests/api_resources/test_model.py
@@ -0,0 +1,160 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import ModelListResponse, ModelDefaultResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestModel:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ model = client.model.list()
+ assert_matches_type(ModelListResponse, model, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ model = client.model.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ModelListResponse, model, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.model.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ model = response.parse()
+ assert_matches_type(ModelListResponse, model, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.model.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ model = response.parse()
+ assert_matches_type(ModelListResponse, model, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_default(self, client: Opencode) -> None:
+ model = client.model.default()
+ assert_matches_type(ModelDefaultResponse, model, path=["response"])
+
+ @parametrize
+ def test_method_default_with_all_params(self, client: Opencode) -> None:
+ model = client.model.default(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ModelDefaultResponse, model, path=["response"])
+
+ @parametrize
+ def test_raw_response_default(self, client: Opencode) -> None:
+ response = client.model.with_raw_response.default()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ model = response.parse()
+ assert_matches_type(ModelDefaultResponse, model, path=["response"])
+
+ @parametrize
+ def test_streaming_response_default(self, client: Opencode) -> None:
+ with client.model.with_streaming_response.default() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ model = response.parse()
+ assert_matches_type(ModelDefaultResponse, model, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncModel:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ model = await async_client.model.list()
+ assert_matches_type(ModelListResponse, model, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ model = await async_client.model.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ModelListResponse, model, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.model.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ model = await response.parse()
+ assert_matches_type(ModelListResponse, model, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.model.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ model = await response.parse()
+ assert_matches_type(ModelListResponse, model, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_default(self, async_client: AsyncOpencode) -> None:
+ model = await async_client.model.default()
+ assert_matches_type(ModelDefaultResponse, model, path=["response"])
+
+ @parametrize
+ async def test_method_default_with_all_params(self, async_client: AsyncOpencode) -> None:
+ model = await async_client.model.default(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ModelDefaultResponse, model, path=["response"])
+
+ @parametrize
+ async def test_raw_response_default(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.model.with_raw_response.default()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ model = await response.parse()
+ assert_matches_type(ModelDefaultResponse, model, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_default(self, async_client: AsyncOpencode) -> None:
+ async with async_client.model.with_streaming_response.default() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ model = await response.parse()
+ assert_matches_type(ModelDefaultResponse, model, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_permission.py b/tests/api_resources/test_permission.py
new file mode 100644
index 0000000..ba28d15
--- /dev/null
+++ b/tests/api_resources/test_permission.py
@@ -0,0 +1,446 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import (
+ PermissionGetResponse,
+ PermissionListResponse,
+ PermissionCreateResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestPermission:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_create(self, client: Opencode) -> None:
+ permission = client.permission.create(
+ session_id={},
+ action="action",
+ resources=["string"],
+ )
+ assert_matches_type(PermissionCreateResponse, permission, path=["response"])
+
+ @parametrize
+ def test_method_create_with_all_params(self, client: Opencode) -> None:
+ permission = client.permission.create(
+ session_id={},
+ action="action",
+ resources=["string"],
+ agent={},
+ id={},
+ metadata={"foo": {}},
+ save=["string"],
+ source={},
+ )
+ assert_matches_type(PermissionCreateResponse, permission, path=["response"])
+
+ @parametrize
+ def test_raw_response_create(self, client: Opencode) -> None:
+ response = client.permission.with_raw_response.create(
+ session_id={},
+ action="action",
+ resources=["string"],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ permission = response.parse()
+ assert_matches_type(PermissionCreateResponse, permission, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create(self, client: Opencode) -> None:
+ with client.permission.with_streaming_response.create(
+ session_id={},
+ action="action",
+ resources=["string"],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ permission = response.parse()
+ assert_matches_type(PermissionCreateResponse, permission, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_create(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.permission.with_raw_response.create(
+ session_id="",
+ action="action",
+ resources=["string"],
+ )
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ permission = client.permission.list(
+ {},
+ )
+ assert_matches_type(PermissionListResponse, permission, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.permission.with_raw_response.list(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ permission = response.parse()
+ assert_matches_type(PermissionListResponse, permission, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.permission.with_streaming_response.list(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ permission = response.parse()
+ assert_matches_type(PermissionListResponse, permission, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_list(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.permission.with_raw_response.list(
+ "",
+ )
+
+ @parametrize
+ def test_method_get(self, client: Opencode) -> None:
+ permission = client.permission.get(
+ request_id={},
+ session_id={},
+ )
+ assert_matches_type(PermissionGetResponse, permission, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Opencode) -> None:
+ response = client.permission.with_raw_response.get(
+ request_id={},
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ permission = response.parse()
+ assert_matches_type(PermissionGetResponse, permission, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Opencode) -> None:
+ with client.permission.with_streaming_response.get(
+ request_id={},
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ permission = response.parse()
+ assert_matches_type(PermissionGetResponse, permission, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_get(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.permission.with_raw_response.get(
+ request_id={},
+ session_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `request_id` but received ''"):
+ client.permission.with_raw_response.get(
+ request_id="",
+ session_id={},
+ )
+
+ @parametrize
+ def test_method_reply(self, client: Opencode) -> None:
+ permission = client.permission.reply(
+ request_id={},
+ session_id={},
+ decision={},
+ )
+ assert permission is None
+
+ @parametrize
+ def test_method_reply_with_all_params(self, client: Opencode) -> None:
+ permission = client.permission.reply(
+ request_id={},
+ session_id={},
+ decision={},
+ message="message",
+ )
+ assert permission is None
+
+ @parametrize
+ def test_raw_response_reply(self, client: Opencode) -> None:
+ response = client.permission.with_raw_response.reply(
+ request_id={},
+ session_id={},
+ decision={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ permission = response.parse()
+ assert permission is None
+
+ @parametrize
+ def test_streaming_response_reply(self, client: Opencode) -> None:
+ with client.permission.with_streaming_response.reply(
+ request_id={},
+ session_id={},
+ decision={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ permission = response.parse()
+ assert permission is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_reply(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.permission.with_raw_response.reply(
+ request_id={},
+ session_id="",
+ decision={},
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `request_id` but received ''"):
+ client.permission.with_raw_response.reply(
+ request_id="",
+ session_id={},
+ decision={},
+ )
+
+
+class TestAsyncPermission:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_create(self, async_client: AsyncOpencode) -> None:
+ permission = await async_client.permission.create(
+ session_id={},
+ action="action",
+ resources=["string"],
+ )
+ assert_matches_type(PermissionCreateResponse, permission, path=["response"])
+
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
+ permission = await async_client.permission.create(
+ session_id={},
+ action="action",
+ resources=["string"],
+ agent={},
+ id={},
+ metadata={"foo": {}},
+ save=["string"],
+ source={},
+ )
+ assert_matches_type(PermissionCreateResponse, permission, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.permission.with_raw_response.create(
+ session_id={},
+ action="action",
+ resources=["string"],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ permission = await response.parse()
+ assert_matches_type(PermissionCreateResponse, permission, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
+ async with async_client.permission.with_streaming_response.create(
+ session_id={},
+ action="action",
+ resources=["string"],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ permission = await response.parse()
+ assert_matches_type(PermissionCreateResponse, permission, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_create(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.permission.with_raw_response.create(
+ session_id="",
+ action="action",
+ resources=["string"],
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ permission = await async_client.permission.list(
+ {},
+ )
+ assert_matches_type(PermissionListResponse, permission, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.permission.with_raw_response.list(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ permission = await response.parse()
+ assert_matches_type(PermissionListResponse, permission, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.permission.with_streaming_response.list(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ permission = await response.parse()
+ assert_matches_type(PermissionListResponse, permission, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_list(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.permission.with_raw_response.list(
+ "",
+ )
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncOpencode) -> None:
+ permission = await async_client.permission.get(
+ request_id={},
+ session_id={},
+ )
+ assert_matches_type(PermissionGetResponse, permission, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.permission.with_raw_response.get(
+ request_id={},
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ permission = await response.parse()
+ assert_matches_type(PermissionGetResponse, permission, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
+ async with async_client.permission.with_streaming_response.get(
+ request_id={},
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ permission = await response.parse()
+ assert_matches_type(PermissionGetResponse, permission, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_get(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.permission.with_raw_response.get(
+ request_id={},
+ session_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `request_id` but received ''"):
+ await async_client.permission.with_raw_response.get(
+ request_id="",
+ session_id={},
+ )
+
+ @parametrize
+ async def test_method_reply(self, async_client: AsyncOpencode) -> None:
+ permission = await async_client.permission.reply(
+ request_id={},
+ session_id={},
+ decision={},
+ )
+ assert permission is None
+
+ @parametrize
+ async def test_method_reply_with_all_params(self, async_client: AsyncOpencode) -> None:
+ permission = await async_client.permission.reply(
+ request_id={},
+ session_id={},
+ decision={},
+ message="message",
+ )
+ assert permission is None
+
+ @parametrize
+ async def test_raw_response_reply(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.permission.with_raw_response.reply(
+ request_id={},
+ session_id={},
+ decision={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ permission = await response.parse()
+ assert permission is None
+
+ @parametrize
+ async def test_streaming_response_reply(self, async_client: AsyncOpencode) -> None:
+ async with async_client.permission.with_streaming_response.reply(
+ request_id={},
+ session_id={},
+ decision={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ permission = await response.parse()
+ assert permission is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_reply(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.permission.with_raw_response.reply(
+ request_id={},
+ session_id="",
+ decision={},
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `request_id` but received ''"):
+ await async_client.permission.with_raw_response.reply(
+ request_id="",
+ session_id={},
+ decision={},
+ )
diff --git a/tests/api_resources/test_plugin.py b/tests/api_resources/test_plugin.py
new file mode 100644
index 0000000..2fc028d
--- /dev/null
+++ b/tests/api_resources/test_plugin.py
@@ -0,0 +1,244 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import PluginListResponse, PluginCheckResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestPlugin:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_update(self, client: Opencode) -> None:
+ plugin = client.plugin.update(
+ targets=["string"],
+ )
+ assert plugin is None
+
+ @parametrize
+ def test_method_update_with_all_params(self, client: Opencode) -> None:
+ plugin = client.plugin.update(
+ targets=["string"],
+ location={
+ "directory": "string",
+ },
+ )
+ assert plugin is None
+
+ @parametrize
+ def test_raw_response_update(self, client: Opencode) -> None:
+ response = client.plugin.with_raw_response.update(
+ targets=["string"],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ plugin = response.parse()
+ assert plugin is None
+
+ @parametrize
+ def test_streaming_response_update(self, client: Opencode) -> None:
+ with client.plugin.with_streaming_response.update(
+ targets=["string"],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ plugin = response.parse()
+ assert plugin is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ plugin = client.plugin.list()
+ assert_matches_type(PluginListResponse, plugin, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ plugin = client.plugin.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(PluginListResponse, plugin, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.plugin.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ plugin = response.parse()
+ assert_matches_type(PluginListResponse, plugin, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.plugin.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ plugin = response.parse()
+ assert_matches_type(PluginListResponse, plugin, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_check(self, client: Opencode) -> None:
+ plugin = client.plugin.check()
+ assert_matches_type(PluginCheckResponse, plugin, path=["response"])
+
+ @parametrize
+ def test_method_check_with_all_params(self, client: Opencode) -> None:
+ plugin = client.plugin.check(
+ location={
+ "directory": "string",
+ },
+ target="target",
+ )
+ assert_matches_type(PluginCheckResponse, plugin, path=["response"])
+
+ @parametrize
+ def test_raw_response_check(self, client: Opencode) -> None:
+ response = client.plugin.with_raw_response.check()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ plugin = response.parse()
+ assert_matches_type(PluginCheckResponse, plugin, path=["response"])
+
+ @parametrize
+ def test_streaming_response_check(self, client: Opencode) -> None:
+ with client.plugin.with_streaming_response.check() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ plugin = response.parse()
+ assert_matches_type(PluginCheckResponse, plugin, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncPlugin:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_update(self, async_client: AsyncOpencode) -> None:
+ plugin = await async_client.plugin.update(
+ targets=["string"],
+ )
+ assert plugin is None
+
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncOpencode) -> None:
+ plugin = await async_client.plugin.update(
+ targets=["string"],
+ location={
+ "directory": "string",
+ },
+ )
+ assert plugin is None
+
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.plugin.with_raw_response.update(
+ targets=["string"],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ plugin = await response.parse()
+ assert plugin is None
+
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncOpencode) -> None:
+ async with async_client.plugin.with_streaming_response.update(
+ targets=["string"],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ plugin = await response.parse()
+ assert plugin is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ plugin = await async_client.plugin.list()
+ assert_matches_type(PluginListResponse, plugin, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ plugin = await async_client.plugin.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(PluginListResponse, plugin, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.plugin.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ plugin = await response.parse()
+ assert_matches_type(PluginListResponse, plugin, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.plugin.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ plugin = await response.parse()
+ assert_matches_type(PluginListResponse, plugin, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_check(self, async_client: AsyncOpencode) -> None:
+ plugin = await async_client.plugin.check()
+ assert_matches_type(PluginCheckResponse, plugin, path=["response"])
+
+ @parametrize
+ async def test_method_check_with_all_params(self, async_client: AsyncOpencode) -> None:
+ plugin = await async_client.plugin.check(
+ location={
+ "directory": "string",
+ },
+ target="target",
+ )
+ assert_matches_type(PluginCheckResponse, plugin, path=["response"])
+
+ @parametrize
+ async def test_raw_response_check(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.plugin.with_raw_response.check()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ plugin = await response.parse()
+ assert_matches_type(PluginCheckResponse, plugin, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_check(self, async_client: AsyncOpencode) -> None:
+ async with async_client.plugin.with_streaming_response.check() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ plugin = await response.parse()
+ assert_matches_type(PluginCheckResponse, plugin, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_project.py b/tests/api_resources/test_project.py
new file mode 100644
index 0000000..ad78721
--- /dev/null
+++ b/tests/api_resources/test_project.py
@@ -0,0 +1,187 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import ProjectListResponse
+from opencode.types.shared import Project
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestProject:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_update(self, client: Opencode) -> None:
+ project = client.project.update(
+ project_id={},
+ )
+ assert_matches_type(Project, project, path=["response"])
+
+ @parametrize
+ def test_method_update_with_all_params(self, client: Opencode) -> None:
+ project = client.project.update(
+ project_id={},
+ canonical={},
+ commands={},
+ icon={},
+ name="name",
+ )
+ assert_matches_type(Project, project, path=["response"])
+
+ @parametrize
+ def test_raw_response_update(self, client: Opencode) -> None:
+ response = client.project.with_raw_response.update(
+ project_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ project = response.parse()
+ assert_matches_type(Project, project, path=["response"])
+
+ @parametrize
+ def test_streaming_response_update(self, client: Opencode) -> None:
+ with client.project.with_streaming_response.update(
+ project_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ project = response.parse()
+ assert_matches_type(Project, project, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_update(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+ client.project.with_raw_response.update(
+ project_id="",
+ )
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ project = client.project.list()
+ assert_matches_type(ProjectListResponse, project, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ project = client.project.list(
+ archived=True,
+ )
+ assert_matches_type(ProjectListResponse, project, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.project.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ project = response.parse()
+ assert_matches_type(ProjectListResponse, project, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.project.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ project = response.parse()
+ assert_matches_type(ProjectListResponse, project, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncProject:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_update(self, async_client: AsyncOpencode) -> None:
+ project = await async_client.project.update(
+ project_id={},
+ )
+ assert_matches_type(Project, project, path=["response"])
+
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncOpencode) -> None:
+ project = await async_client.project.update(
+ project_id={},
+ canonical={},
+ commands={},
+ icon={},
+ name="name",
+ )
+ assert_matches_type(Project, project, path=["response"])
+
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.project.with_raw_response.update(
+ project_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ project = await response.parse()
+ assert_matches_type(Project, project, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncOpencode) -> None:
+ async with async_client.project.with_streaming_response.update(
+ project_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ project = await response.parse()
+ assert_matches_type(Project, project, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `project_id` but received ''"):
+ await async_client.project.with_raw_response.update(
+ project_id="",
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ project = await async_client.project.list()
+ assert_matches_type(ProjectListResponse, project, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ project = await async_client.project.list(
+ archived=True,
+ )
+ assert_matches_type(ProjectListResponse, project, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.project.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ project = await response.parse()
+ assert_matches_type(ProjectListResponse, project, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.project.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ project = await response.parse()
+ assert_matches_type(ProjectListResponse, project, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_provider.py b/tests/api_resources/test_provider.py
new file mode 100644
index 0000000..9a0ef40
--- /dev/null
+++ b/tests/api_resources/test_provider.py
@@ -0,0 +1,188 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import ProviderGetResponse, ProviderListResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestProvider:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ provider = client.provider.list()
+ assert_matches_type(ProviderListResponse, provider, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ provider = client.provider.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ProviderListResponse, provider, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.provider.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ provider = response.parse()
+ assert_matches_type(ProviderListResponse, provider, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.provider.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ provider = response.parse()
+ assert_matches_type(ProviderListResponse, provider, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_get(self, client: Opencode) -> None:
+ provider = client.provider.get(
+ provider_id={},
+ )
+ assert_matches_type(ProviderGetResponse, provider, path=["response"])
+
+ @parametrize
+ def test_method_get_with_all_params(self, client: Opencode) -> None:
+ provider = client.provider.get(
+ provider_id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ProviderGetResponse, provider, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Opencode) -> None:
+ response = client.provider.with_raw_response.get(
+ provider_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ provider = response.parse()
+ assert_matches_type(ProviderGetResponse, provider, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Opencode) -> None:
+ with client.provider.with_streaming_response.get(
+ provider_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ provider = response.parse()
+ assert_matches_type(ProviderGetResponse, provider, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_get(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `provider_id` but received ''"):
+ client.provider.with_raw_response.get(
+ provider_id="",
+ )
+
+
+class TestAsyncProvider:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ provider = await async_client.provider.list()
+ assert_matches_type(ProviderListResponse, provider, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ provider = await async_client.provider.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ProviderListResponse, provider, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.provider.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ provider = await response.parse()
+ assert_matches_type(ProviderListResponse, provider, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.provider.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ provider = await response.parse()
+ assert_matches_type(ProviderListResponse, provider, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncOpencode) -> None:
+ provider = await async_client.provider.get(
+ provider_id={},
+ )
+ assert_matches_type(ProviderGetResponse, provider, path=["response"])
+
+ @parametrize
+ async def test_method_get_with_all_params(self, async_client: AsyncOpencode) -> None:
+ provider = await async_client.provider.get(
+ provider_id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ProviderGetResponse, provider, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.provider.with_raw_response.get(
+ provider_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ provider = await response.parse()
+ assert_matches_type(ProviderGetResponse, provider, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
+ async with async_client.provider.with_streaming_response.get(
+ provider_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ provider = await response.parse()
+ assert_matches_type(ProviderGetResponse, provider, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_get(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `provider_id` but received ''"):
+ await async_client.provider.with_raw_response.get(
+ provider_id="",
+ )
diff --git a/tests/api_resources/test_pty.py b/tests/api_resources/test_pty.py
new file mode 100644
index 0000000..f869d19
--- /dev/null
+++ b/tests/api_resources/test_pty.py
@@ -0,0 +1,473 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import (
+ PtyGetResponse,
+ PtyListResponse,
+ PtyCreateResponse,
+ PtyUpdateResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestPty:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_create(self, client: Opencode) -> None:
+ pty = client.pty.create()
+ assert_matches_type(PtyCreateResponse, pty, path=["response"])
+
+ @parametrize
+ def test_method_create_with_all_params(self, client: Opencode) -> None:
+ pty = client.pty.create(
+ args=["string"],
+ command="command",
+ cwd="cwd",
+ env={"foo": "string"},
+ location={
+ "directory": "string",
+ },
+ title="title",
+ )
+ assert_matches_type(PtyCreateResponse, pty, path=["response"])
+
+ @parametrize
+ def test_raw_response_create(self, client: Opencode) -> None:
+ response = client.pty.with_raw_response.create()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ pty = response.parse()
+ assert_matches_type(PtyCreateResponse, pty, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create(self, client: Opencode) -> None:
+ with client.pty.with_streaming_response.create() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ pty = response.parse()
+ assert_matches_type(PtyCreateResponse, pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_update(self, client: Opencode) -> None:
+ pty = client.pty.update(
+ pty_id={},
+ )
+ assert_matches_type(PtyUpdateResponse, pty, path=["response"])
+
+ @parametrize
+ def test_method_update_with_all_params(self, client: Opencode) -> None:
+ pty = client.pty.update(
+ pty_id={},
+ location={
+ "directory": "string",
+ },
+ size={
+ "cols": 0,
+ "rows": 0,
+ },
+ title="title",
+ )
+ assert_matches_type(PtyUpdateResponse, pty, path=["response"])
+
+ @parametrize
+ def test_raw_response_update(self, client: Opencode) -> None:
+ response = client.pty.with_raw_response.update(
+ pty_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ pty = response.parse()
+ assert_matches_type(PtyUpdateResponse, pty, path=["response"])
+
+ @parametrize
+ def test_streaming_response_update(self, client: Opencode) -> None:
+ with client.pty.with_streaming_response.update(
+ pty_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ pty = response.parse()
+ assert_matches_type(PtyUpdateResponse, pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_update(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `pty_id` but received ''"):
+ client.pty.with_raw_response.update(
+ pty_id="",
+ )
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ pty = client.pty.list()
+ assert_matches_type(PtyListResponse, pty, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ pty = client.pty.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(PtyListResponse, pty, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.pty.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ pty = response.parse()
+ assert_matches_type(PtyListResponse, pty, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.pty.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ pty = response.parse()
+ assert_matches_type(PtyListResponse, pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_get(self, client: Opencode) -> None:
+ pty = client.pty.get(
+ pty_id={},
+ )
+ assert_matches_type(PtyGetResponse, pty, path=["response"])
+
+ @parametrize
+ def test_method_get_with_all_params(self, client: Opencode) -> None:
+ pty = client.pty.get(
+ pty_id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(PtyGetResponse, pty, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Opencode) -> None:
+ response = client.pty.with_raw_response.get(
+ pty_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ pty = response.parse()
+ assert_matches_type(PtyGetResponse, pty, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Opencode) -> None:
+ with client.pty.with_streaming_response.get(
+ pty_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ pty = response.parse()
+ assert_matches_type(PtyGetResponse, pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_get(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `pty_id` but received ''"):
+ client.pty.with_raw_response.get(
+ pty_id="",
+ )
+
+ @parametrize
+ def test_method_remove(self, client: Opencode) -> None:
+ pty = client.pty.remove(
+ pty_id={},
+ )
+ assert pty is None
+
+ @parametrize
+ def test_method_remove_with_all_params(self, client: Opencode) -> None:
+ pty = client.pty.remove(
+ pty_id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert pty is None
+
+ @parametrize
+ def test_raw_response_remove(self, client: Opencode) -> None:
+ response = client.pty.with_raw_response.remove(
+ pty_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ pty = response.parse()
+ assert pty is None
+
+ @parametrize
+ def test_streaming_response_remove(self, client: Opencode) -> None:
+ with client.pty.with_streaming_response.remove(
+ pty_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ pty = response.parse()
+ assert pty is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_remove(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `pty_id` but received ''"):
+ client.pty.with_raw_response.remove(
+ pty_id="",
+ )
+
+
+class TestAsyncPty:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_create(self, async_client: AsyncOpencode) -> None:
+ pty = await async_client.pty.create()
+ assert_matches_type(PtyCreateResponse, pty, path=["response"])
+
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
+ pty = await async_client.pty.create(
+ args=["string"],
+ command="command",
+ cwd="cwd",
+ env={"foo": "string"},
+ location={
+ "directory": "string",
+ },
+ title="title",
+ )
+ assert_matches_type(PtyCreateResponse, pty, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.pty.with_raw_response.create()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ pty = await response.parse()
+ assert_matches_type(PtyCreateResponse, pty, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
+ async with async_client.pty.with_streaming_response.create() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ pty = await response.parse()
+ assert_matches_type(PtyCreateResponse, pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_update(self, async_client: AsyncOpencode) -> None:
+ pty = await async_client.pty.update(
+ pty_id={},
+ )
+ assert_matches_type(PtyUpdateResponse, pty, path=["response"])
+
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncOpencode) -> None:
+ pty = await async_client.pty.update(
+ pty_id={},
+ location={
+ "directory": "string",
+ },
+ size={
+ "cols": 0,
+ "rows": 0,
+ },
+ title="title",
+ )
+ assert_matches_type(PtyUpdateResponse, pty, path=["response"])
+
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.pty.with_raw_response.update(
+ pty_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ pty = await response.parse()
+ assert_matches_type(PtyUpdateResponse, pty, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncOpencode) -> None:
+ async with async_client.pty.with_streaming_response.update(
+ pty_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ pty = await response.parse()
+ assert_matches_type(PtyUpdateResponse, pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `pty_id` but received ''"):
+ await async_client.pty.with_raw_response.update(
+ pty_id="",
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ pty = await async_client.pty.list()
+ assert_matches_type(PtyListResponse, pty, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ pty = await async_client.pty.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(PtyListResponse, pty, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.pty.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ pty = await response.parse()
+ assert_matches_type(PtyListResponse, pty, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.pty.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ pty = await response.parse()
+ assert_matches_type(PtyListResponse, pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncOpencode) -> None:
+ pty = await async_client.pty.get(
+ pty_id={},
+ )
+ assert_matches_type(PtyGetResponse, pty, path=["response"])
+
+ @parametrize
+ async def test_method_get_with_all_params(self, async_client: AsyncOpencode) -> None:
+ pty = await async_client.pty.get(
+ pty_id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(PtyGetResponse, pty, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.pty.with_raw_response.get(
+ pty_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ pty = await response.parse()
+ assert_matches_type(PtyGetResponse, pty, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
+ async with async_client.pty.with_streaming_response.get(
+ pty_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ pty = await response.parse()
+ assert_matches_type(PtyGetResponse, pty, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_get(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `pty_id` but received ''"):
+ await async_client.pty.with_raw_response.get(
+ pty_id="",
+ )
+
+ @parametrize
+ async def test_method_remove(self, async_client: AsyncOpencode) -> None:
+ pty = await async_client.pty.remove(
+ pty_id={},
+ )
+ assert pty is None
+
+ @parametrize
+ async def test_method_remove_with_all_params(self, async_client: AsyncOpencode) -> None:
+ pty = await async_client.pty.remove(
+ pty_id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert pty is None
+
+ @parametrize
+ async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.pty.with_raw_response.remove(
+ pty_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ pty = await response.parse()
+ assert pty is None
+
+ @parametrize
+ async def test_streaming_response_remove(self, async_client: AsyncOpencode) -> None:
+ async with async_client.pty.with_streaming_response.remove(
+ pty_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ pty = await response.parse()
+ assert pty is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_remove(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `pty_id` but received ''"):
+ await async_client.pty.with_raw_response.remove(
+ pty_id="",
+ )
diff --git a/tests/api_resources/test_reference.py b/tests/api_resources/test_reference.py
new file mode 100644
index 0000000..8a343b3
--- /dev/null
+++ b/tests/api_resources/test_reference.py
@@ -0,0 +1,92 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import ReferenceListResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestReference:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ reference = client.reference.list()
+ assert_matches_type(ReferenceListResponse, reference, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ reference = client.reference.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ReferenceListResponse, reference, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.reference.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ reference = response.parse()
+ assert_matches_type(ReferenceListResponse, reference, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.reference.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ reference = response.parse()
+ assert_matches_type(ReferenceListResponse, reference, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncReference:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ reference = await async_client.reference.list()
+ assert_matches_type(ReferenceListResponse, reference, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ reference = await async_client.reference.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ReferenceListResponse, reference, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.reference.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ reference = await response.parse()
+ assert_matches_type(ReferenceListResponse, reference, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.reference.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ reference = await response.parse()
+ assert_matches_type(ReferenceListResponse, reference, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_rpc.py b/tests/api_resources/test_rpc.py
new file mode 100644
index 0000000..2ae5c1a
--- /dev/null
+++ b/tests/api_resources/test_rpc.py
@@ -0,0 +1,144 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.shared import RpcOutput
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestRpc:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_call(self, client: Opencode) -> None:
+ rpc = client.rpc.call(
+ method="method",
+ rpc_id="rpcID",
+ )
+ assert_matches_type(RpcOutput, rpc, path=["response"])
+
+ @parametrize
+ def test_method_call_with_all_params(self, client: Opencode) -> None:
+ rpc = client.rpc.call(
+ method="method",
+ rpc_id="rpcID",
+ input={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(RpcOutput, rpc, path=["response"])
+
+ @parametrize
+ def test_raw_response_call(self, client: Opencode) -> None:
+ response = client.rpc.with_raw_response.call(
+ method="method",
+ rpc_id="rpcID",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ rpc = response.parse()
+ assert_matches_type(RpcOutput, rpc, path=["response"])
+
+ @parametrize
+ def test_streaming_response_call(self, client: Opencode) -> None:
+ with client.rpc.with_streaming_response.call(
+ method="method",
+ rpc_id="rpcID",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ rpc = response.parse()
+ assert_matches_type(RpcOutput, rpc, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_call(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `rpc_id` but received ''"):
+ client.rpc.with_raw_response.call(
+ method="method",
+ rpc_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `method` but received ''"):
+ client.rpc.with_raw_response.call(
+ method="",
+ rpc_id="rpcID",
+ )
+
+
+class TestAsyncRpc:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_call(self, async_client: AsyncOpencode) -> None:
+ rpc = await async_client.rpc.call(
+ method="method",
+ rpc_id="rpcID",
+ )
+ assert_matches_type(RpcOutput, rpc, path=["response"])
+
+ @parametrize
+ async def test_method_call_with_all_params(self, async_client: AsyncOpencode) -> None:
+ rpc = await async_client.rpc.call(
+ method="method",
+ rpc_id="rpcID",
+ input={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(RpcOutput, rpc, path=["response"])
+
+ @parametrize
+ async def test_raw_response_call(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.rpc.with_raw_response.call(
+ method="method",
+ rpc_id="rpcID",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ rpc = await response.parse()
+ assert_matches_type(RpcOutput, rpc, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_call(self, async_client: AsyncOpencode) -> None:
+ async with async_client.rpc.with_streaming_response.call(
+ method="method",
+ rpc_id="rpcID",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ rpc = await response.parse()
+ assert_matches_type(RpcOutput, rpc, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_call(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `rpc_id` but received ''"):
+ await async_client.rpc.with_raw_response.call(
+ method="method",
+ rpc_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `method` but received ''"):
+ await async_client.rpc.with_raw_response.call(
+ method="",
+ rpc_id="rpcID",
+ )
diff --git a/tests/api_resources/test_server.py b/tests/api_resources/test_server.py
new file mode 100644
index 0000000..48965bf
--- /dev/null
+++ b/tests/api_resources/test_server.py
@@ -0,0 +1,200 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.shared import ServerInfo, PairingCode, PairingSession
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestServer:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_connect(self, client: Opencode) -> None:
+ server = client.server.connect(
+ "code",
+ )
+ assert_matches_type(PairingSession, server, path=["response"])
+
+ @parametrize
+ def test_raw_response_connect(self, client: Opencode) -> None:
+ response = client.server.with_raw_response.connect(
+ "code",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ server = response.parse()
+ assert_matches_type(PairingSession, server, path=["response"])
+
+ @parametrize
+ def test_streaming_response_connect(self, client: Opencode) -> None:
+ with client.server.with_streaming_response.connect(
+ "code",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ server = response.parse()
+ assert_matches_type(PairingSession, server, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_connect(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `code` but received ''"):
+ client.server.with_raw_response.connect(
+ "",
+ )
+
+ @parametrize
+ def test_method_info(self, client: Opencode) -> None:
+ server = client.server.info()
+ assert_matches_type(ServerInfo, server, path=["response"])
+
+ @parametrize
+ def test_raw_response_info(self, client: Opencode) -> None:
+ response = client.server.with_raw_response.info()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ server = response.parse()
+ assert_matches_type(ServerInfo, server, path=["response"])
+
+ @parametrize
+ def test_streaming_response_info(self, client: Opencode) -> None:
+ with client.server.with_streaming_response.info() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ server = response.parse()
+ assert_matches_type(ServerInfo, server, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_pair(self, client: Opencode) -> None:
+ server = client.server.pair()
+ assert_matches_type(PairingCode, server, path=["response"])
+
+ @parametrize
+ def test_raw_response_pair(self, client: Opencode) -> None:
+ response = client.server.with_raw_response.pair()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ server = response.parse()
+ assert_matches_type(PairingCode, server, path=["response"])
+
+ @parametrize
+ def test_streaming_response_pair(self, client: Opencode) -> None:
+ with client.server.with_streaming_response.pair() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ server = response.parse()
+ assert_matches_type(PairingCode, server, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncServer:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_connect(self, async_client: AsyncOpencode) -> None:
+ server = await async_client.server.connect(
+ "code",
+ )
+ assert_matches_type(PairingSession, server, path=["response"])
+
+ @parametrize
+ async def test_raw_response_connect(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.server.with_raw_response.connect(
+ "code",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ server = await response.parse()
+ assert_matches_type(PairingSession, server, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_connect(self, async_client: AsyncOpencode) -> None:
+ async with async_client.server.with_streaming_response.connect(
+ "code",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ server = await response.parse()
+ assert_matches_type(PairingSession, server, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_connect(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `code` but received ''"):
+ await async_client.server.with_raw_response.connect(
+ "",
+ )
+
+ @parametrize
+ async def test_method_info(self, async_client: AsyncOpencode) -> None:
+ server = await async_client.server.info()
+ assert_matches_type(ServerInfo, server, path=["response"])
+
+ @parametrize
+ async def test_raw_response_info(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.server.with_raw_response.info()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ server = await response.parse()
+ assert_matches_type(ServerInfo, server, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_info(self, async_client: AsyncOpencode) -> None:
+ async with async_client.server.with_streaming_response.info() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ server = await response.parse()
+ assert_matches_type(ServerInfo, server, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_pair(self, async_client: AsyncOpencode) -> None:
+ server = await async_client.server.pair()
+ assert_matches_type(PairingCode, server, path=["response"])
+
+ @parametrize
+ async def test_raw_response_pair(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.server.with_raw_response.pair()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ server = await response.parse()
+ assert_matches_type(PairingCode, server, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_pair(self, async_client: AsyncOpencode) -> None:
+ async with async_client.server.with_streaming_response.pair() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ server = await response.parse()
+ assert_matches_type(PairingCode, server, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_session.py b/tests/api_resources/test_session.py
new file mode 100644
index 0000000..1894694
--- /dev/null
+++ b/tests/api_resources/test_session.py
@@ -0,0 +1,2514 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import (
+ SessionGetResponse,
+ SessionDiffResponse,
+ SessionForkResponse,
+ SessionStatsResponse,
+ SessionActiveResponse,
+ SessionCreateResponse,
+ SessionExportResponse,
+ SessionImportResponse,
+ SessionPromptResponse,
+ SessionCompactResponse,
+ SessionContextResponse,
+ SessionSyntheticResponse,
+)
+from opencode.types.shared import (
+ SessionInfo,
+ SessionGenerateResponse,
+ SessionInterruptResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestSession:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_create(self, client: Opencode) -> None:
+ session = client.session.create()
+ assert_matches_type(SessionCreateResponse, session, path=["response"])
+
+ @parametrize
+ def test_method_create_with_all_params(self, client: Opencode) -> None:
+ session = client.session.create(
+ agent={},
+ id={},
+ location={},
+ metadata={},
+ model={},
+ parent_id={},
+ permissions={},
+ title="title",
+ )
+ assert_matches_type(SessionCreateResponse, session, path=["response"])
+
+ @parametrize
+ def test_raw_response_create(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.create()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert_matches_type(SessionCreateResponse, session, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.create() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert_matches_type(SessionCreateResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_update(self, client: Opencode) -> None:
+ session = client.session.update(
+ session_id={},
+ )
+ assert session is None
+
+ @parametrize
+ def test_method_update_with_all_params(self, client: Opencode) -> None:
+ session = client.session.update(
+ session_id={},
+ archived=True,
+ metadata={},
+ permissions={},
+ title="title",
+ )
+ assert session is None
+
+ @parametrize
+ def test_raw_response_update(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.update(
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert session is None
+
+ @parametrize
+ def test_streaming_response_update(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.update(
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_update(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.update(
+ session_id="",
+ )
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ session = client.session.list()
+ assert_matches_type(SessionInfo, session, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ session = client.session.list(
+ archived=True,
+ cursor={},
+ directory={},
+ limit=0,
+ order="asc",
+ parent_id={},
+ project={},
+ search="search",
+ subpath={},
+ )
+ assert_matches_type(SessionInfo, session, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert_matches_type(SessionInfo, session, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert_matches_type(SessionInfo, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_active(self, client: Opencode) -> None:
+ session = client.session.active()
+ assert_matches_type(SessionActiveResponse, session, path=["response"])
+
+ @parametrize
+ def test_raw_response_active(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.active()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert_matches_type(SessionActiveResponse, session, path=["response"])
+
+ @parametrize
+ def test_streaming_response_active(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.active() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert_matches_type(SessionActiveResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_background(self, client: Opencode) -> None:
+ session = client.session.background(
+ {},
+ )
+ assert session is None
+
+ @parametrize
+ def test_raw_response_background(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.background(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert session is None
+
+ @parametrize
+ def test_streaming_response_background(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.background(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_background(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.background(
+ "",
+ )
+
+ @parametrize
+ def test_method_command(self, client: Opencode) -> None:
+ session = client.session.command(
+ session_id={},
+ name="name",
+ text="text",
+ )
+ assert session is None
+
+ @parametrize
+ def test_method_command_with_all_params(self, client: Opencode) -> None:
+ session = client.session.command(
+ session_id={},
+ name="name",
+ text="text",
+ agents=[{}],
+ delivery={},
+ files=[{}],
+ skills=[{}],
+ )
+ assert session is None
+
+ @parametrize
+ def test_raw_response_command(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.command(
+ session_id={},
+ name="name",
+ text="text",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert session is None
+
+ @parametrize
+ def test_streaming_response_command(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.command(
+ session_id={},
+ name="name",
+ text="text",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_command(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.command(
+ session_id="",
+ name="name",
+ text="text",
+ )
+
+ @parametrize
+ def test_method_compact(self, client: Opencode) -> None:
+ session = client.session.compact(
+ session_id={},
+ )
+ assert_matches_type(SessionCompactResponse, session, path=["response"])
+
+ @parametrize
+ def test_method_compact_with_all_params(self, client: Opencode) -> None:
+ session = client.session.compact(
+ session_id={},
+ delivery={},
+ id={},
+ )
+ assert_matches_type(SessionCompactResponse, session, path=["response"])
+
+ @parametrize
+ def test_raw_response_compact(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.compact(
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert_matches_type(SessionCompactResponse, session, path=["response"])
+
+ @parametrize
+ def test_streaming_response_compact(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.compact(
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert_matches_type(SessionCompactResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_compact(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.compact(
+ session_id="",
+ )
+
+ @parametrize
+ def test_method_context(self, client: Opencode) -> None:
+ session = client.session.context(
+ {},
+ )
+ assert_matches_type(SessionContextResponse, session, path=["response"])
+
+ @parametrize
+ def test_raw_response_context(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.context(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert_matches_type(SessionContextResponse, session, path=["response"])
+
+ @parametrize
+ def test_streaming_response_context(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.context(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert_matches_type(SessionContextResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_context(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.context(
+ "",
+ )
+
+ @parametrize
+ def test_method_diff(self, client: Opencode) -> None:
+ session = client.session.diff(
+ session_id={},
+ )
+ assert_matches_type(SessionDiffResponse, session, path=["response"])
+
+ @parametrize
+ def test_method_diff_with_all_params(self, client: Opencode) -> None:
+ session = client.session.diff(
+ session_id={},
+ context=0,
+ from_={},
+ to={},
+ )
+ assert_matches_type(SessionDiffResponse, session, path=["response"])
+
+ @parametrize
+ def test_raw_response_diff(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.diff(
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert_matches_type(SessionDiffResponse, session, path=["response"])
+
+ @parametrize
+ def test_streaming_response_diff(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.diff(
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert_matches_type(SessionDiffResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_diff(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.diff(
+ session_id="",
+ )
+
+ @parametrize
+ def test_method_environment(self, client: Opencode) -> None:
+ session = client.session.environment(
+ session_id={},
+ variables={"foo": "string"},
+ )
+ assert session is None
+
+ @parametrize
+ def test_raw_response_environment(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.environment(
+ session_id={},
+ variables={"foo": "string"},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert session is None
+
+ @parametrize
+ def test_streaming_response_environment(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.environment(
+ session_id={},
+ variables={"foo": "string"},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_environment(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.environment(
+ session_id="",
+ variables={"foo": "string"},
+ )
+
+ @parametrize
+ def test_method_export(self, client: Opencode) -> None:
+ session = client.session.export(
+ session_id={},
+ )
+ assert_matches_type(SessionExportResponse, session, path=["response"])
+
+ @parametrize
+ def test_method_export_with_all_params(self, client: Opencode) -> None:
+ session = client.session.export(
+ session_id={},
+ sanitize=True,
+ )
+ assert_matches_type(SessionExportResponse, session, path=["response"])
+
+ @parametrize
+ def test_raw_response_export(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.export(
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert_matches_type(SessionExportResponse, session, path=["response"])
+
+ @parametrize
+ def test_streaming_response_export(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.export(
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert_matches_type(SessionExportResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_export(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.export(
+ session_id="",
+ )
+
+ @parametrize
+ def test_method_fork(self, client: Opencode) -> None:
+ session = client.session.fork(
+ session_id={},
+ )
+ assert_matches_type(SessionForkResponse, session, path=["response"])
+
+ @parametrize
+ def test_method_fork_with_all_params(self, client: Opencode) -> None:
+ session = client.session.fork(
+ session_id={},
+ before={},
+ )
+ assert_matches_type(SessionForkResponse, session, path=["response"])
+
+ @parametrize
+ def test_raw_response_fork(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.fork(
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert_matches_type(SessionForkResponse, session, path=["response"])
+
+ @parametrize
+ def test_streaming_response_fork(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.fork(
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert_matches_type(SessionForkResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_fork(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.fork(
+ session_id="",
+ )
+
+ @parametrize
+ def test_method_generate(self, client: Opencode) -> None:
+ session = client.session.generate(
+ session_id={},
+ prompt="prompt",
+ )
+ assert_matches_type(SessionGenerateResponse, session, path=["response"])
+
+ @parametrize
+ def test_raw_response_generate(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.generate(
+ session_id={},
+ prompt="prompt",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert_matches_type(SessionGenerateResponse, session, path=["response"])
+
+ @parametrize
+ def test_streaming_response_generate(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.generate(
+ session_id={},
+ prompt="prompt",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert_matches_type(SessionGenerateResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_generate(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.generate(
+ session_id="",
+ prompt="prompt",
+ )
+
+ @parametrize
+ def test_method_get(self, client: Opencode) -> None:
+ session = client.session.get(
+ {},
+ )
+ assert_matches_type(SessionGetResponse, session, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.get(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert_matches_type(SessionGetResponse, session, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.get(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert_matches_type(SessionGetResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_get(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.get(
+ "",
+ )
+
+ @parametrize
+ def test_method_import_(self, client: Opencode) -> None:
+ session = client.session.import_(
+ info={},
+ messages=[{}],
+ )
+ assert_matches_type(SessionImportResponse, session, path=["response"])
+
+ @parametrize
+ def test_method_import__with_all_params(self, client: Opencode) -> None:
+ session = client.session.import_(
+ info={},
+ messages=[{}],
+ location={},
+ )
+ assert_matches_type(SessionImportResponse, session, path=["response"])
+
+ @parametrize
+ def test_raw_response_import_(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.import_(
+ info={},
+ messages=[{}],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert_matches_type(SessionImportResponse, session, path=["response"])
+
+ @parametrize
+ def test_streaming_response_import_(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.import_(
+ info={},
+ messages=[{}],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert_matches_type(SessionImportResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_interrupt(self, client: Opencode) -> None:
+ session = client.session.interrupt(
+ session_id={},
+ )
+ assert_matches_type(SessionInterruptResponse, session, path=["response"])
+
+ @parametrize
+ def test_method_interrupt_with_all_params(self, client: Opencode) -> None:
+ session = client.session.interrupt(
+ session_id={},
+ resume=True,
+ )
+ assert_matches_type(SessionInterruptResponse, session, path=["response"])
+
+ @parametrize
+ def test_raw_response_interrupt(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.interrupt(
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert_matches_type(SessionInterruptResponse, session, path=["response"])
+
+ @parametrize
+ def test_streaming_response_interrupt(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.interrupt(
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert_matches_type(SessionInterruptResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_interrupt(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.interrupt(
+ session_id="",
+ )
+
+ @parametrize
+ def test_method_log(self, client: Opencode) -> None:
+ session = client.session.log(
+ session_id={},
+ )
+ assert session is None
+
+ @parametrize
+ def test_method_log_with_all_params(self, client: Opencode) -> None:
+ session = client.session.log(
+ session_id={},
+ after={},
+ follow=True,
+ )
+ assert session is None
+
+ @parametrize
+ def test_raw_response_log(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.log(
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert session is None
+
+ @parametrize
+ def test_streaming_response_log(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.log(
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_log(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.log(
+ session_id="",
+ )
+
+ @parametrize
+ def test_method_move(self, client: Opencode) -> None:
+ session = client.session.move(
+ session_id={},
+ directory={},
+ )
+ assert session is None
+
+ @parametrize
+ def test_method_move_with_all_params(self, client: Opencode) -> None:
+ session = client.session.move(
+ session_id={},
+ directory={},
+ delivery={},
+ )
+ assert session is None
+
+ @parametrize
+ def test_raw_response_move(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.move(
+ session_id={},
+ directory={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert session is None
+
+ @parametrize
+ def test_streaming_response_move(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.move(
+ session_id={},
+ directory={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_move(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.move(
+ session_id="",
+ directory={},
+ )
+
+ @parametrize
+ def test_method_prompt(self, client: Opencode) -> None:
+ session = client.session.prompt(
+ session_id={},
+ text="text",
+ )
+ assert_matches_type(SessionPromptResponse, session, path=["response"])
+
+ @parametrize
+ def test_method_prompt_with_all_params(self, client: Opencode) -> None:
+ session = client.session.prompt(
+ session_id={},
+ text="text",
+ agents=[{}],
+ delivery={},
+ files=[{}],
+ id={},
+ metadata={"foo": {}},
+ resume=True,
+ skills=[{}],
+ )
+ assert_matches_type(SessionPromptResponse, session, path=["response"])
+
+ @parametrize
+ def test_raw_response_prompt(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.prompt(
+ session_id={},
+ text="text",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert_matches_type(SessionPromptResponse, session, path=["response"])
+
+ @parametrize
+ def test_streaming_response_prompt(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.prompt(
+ session_id={},
+ text="text",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert_matches_type(SessionPromptResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_prompt(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.prompt(
+ session_id="",
+ text="text",
+ )
+
+ @parametrize
+ def test_method_remove(self, client: Opencode) -> None:
+ session = client.session.remove(
+ {},
+ )
+ assert session is None
+
+ @parametrize
+ def test_raw_response_remove(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.remove(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert session is None
+
+ @parametrize
+ def test_streaming_response_remove(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.remove(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_remove(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.remove(
+ "",
+ )
+
+ @parametrize
+ def test_method_shell(self, client: Opencode) -> None:
+ session = client.session.shell(
+ session_id={},
+ command="command",
+ )
+ assert session is None
+
+ @parametrize
+ def test_method_shell_with_all_params(self, client: Opencode) -> None:
+ session = client.session.shell(
+ session_id={},
+ command="command",
+ id={},
+ )
+ assert session is None
+
+ @parametrize
+ def test_raw_response_shell(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.shell(
+ session_id={},
+ command="command",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert session is None
+
+ @parametrize
+ def test_streaming_response_shell(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.shell(
+ session_id={},
+ command="command",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_shell(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.shell(
+ session_id="",
+ command="command",
+ )
+
+ @parametrize
+ def test_method_skill(self, client: Opencode) -> None:
+ session = client.session.skill(
+ session_id={},
+ id={},
+ )
+ assert session is None
+
+ @parametrize
+ def test_method_skill_with_all_params(self, client: Opencode) -> None:
+ session = client.session.skill(
+ session_id={},
+ id={},
+ resume=True,
+ )
+ assert session is None
+
+ @parametrize
+ def test_raw_response_skill(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.skill(
+ session_id={},
+ id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert session is None
+
+ @parametrize
+ def test_streaming_response_skill(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.skill(
+ session_id={},
+ id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_skill(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.skill(
+ session_id="",
+ id={},
+ )
+
+ @parametrize
+ def test_method_stats(self, client: Opencode) -> None:
+ session = client.session.stats()
+ assert_matches_type(SessionStatsResponse, session, path=["response"])
+
+ @parametrize
+ def test_method_stats_with_all_params(self, client: Opencode) -> None:
+ session = client.session.stats(
+ from_=0,
+ project={},
+ timezone="timezone",
+ to=0,
+ tools="none",
+ )
+ assert_matches_type(SessionStatsResponse, session, path=["response"])
+
+ @parametrize
+ def test_raw_response_stats(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.stats()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert_matches_type(SessionStatsResponse, session, path=["response"])
+
+ @parametrize
+ def test_streaming_response_stats(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.stats() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert_matches_type(SessionStatsResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_switch_agent(self, client: Opencode) -> None:
+ session = client.session.switch_agent(
+ session_id={},
+ agent={},
+ )
+ assert session is None
+
+ @parametrize
+ def test_raw_response_switch_agent(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.switch_agent(
+ session_id={},
+ agent={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert session is None
+
+ @parametrize
+ def test_streaming_response_switch_agent(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.switch_agent(
+ session_id={},
+ agent={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_switch_agent(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.switch_agent(
+ session_id="",
+ agent={},
+ )
+
+ @parametrize
+ def test_method_switch_model(self, client: Opencode) -> None:
+ session = client.session.switch_model(
+ session_id={},
+ model={},
+ )
+ assert session is None
+
+ @parametrize
+ def test_raw_response_switch_model(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.switch_model(
+ session_id={},
+ model={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert session is None
+
+ @parametrize
+ def test_streaming_response_switch_model(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.switch_model(
+ session_id={},
+ model={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_switch_model(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.switch_model(
+ session_id="",
+ model={},
+ )
+
+ @parametrize
+ def test_method_synthetic(self, client: Opencode) -> None:
+ session = client.session.synthetic(
+ session_id={},
+ text="text",
+ )
+ assert_matches_type(SessionSyntheticResponse, session, path=["response"])
+
+ @parametrize
+ def test_method_synthetic_with_all_params(self, client: Opencode) -> None:
+ session = client.session.synthetic(
+ session_id={},
+ text="text",
+ delivery={},
+ description="description",
+ id={},
+ metadata={"foo": {}},
+ resume=True,
+ )
+ assert_matches_type(SessionSyntheticResponse, session, path=["response"])
+
+ @parametrize
+ def test_raw_response_synthetic(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.synthetic(
+ session_id={},
+ text="text",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert_matches_type(SessionSyntheticResponse, session, path=["response"])
+
+ @parametrize
+ def test_streaming_response_synthetic(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.synthetic(
+ session_id={},
+ text="text",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert_matches_type(SessionSyntheticResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_synthetic(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.synthetic(
+ session_id="",
+ text="text",
+ )
+
+ @parametrize
+ def test_method_view(self, client: Opencode) -> None:
+ session = client.session.view(
+ session_id={},
+ idle=0,
+ )
+ assert session is None
+
+ @parametrize
+ def test_raw_response_view(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.view(
+ session_id={},
+ idle=0,
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert session is None
+
+ @parametrize
+ def test_streaming_response_view(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.view(
+ session_id={},
+ idle=0,
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_view(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.view(
+ session_id="",
+ idle=0,
+ )
+
+ @parametrize
+ def test_method_wait(self, client: Opencode) -> None:
+ session = client.session.wait(
+ {},
+ )
+ assert session is None
+
+ @parametrize
+ def test_raw_response_wait(self, client: Opencode) -> None:
+ response = client.session.with_raw_response.wait(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = response.parse()
+ assert session is None
+
+ @parametrize
+ def test_streaming_response_wait(self, client: Opencode) -> None:
+ with client.session.with_streaming_response.wait(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_wait(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ client.session.with_raw_response.wait(
+ "",
+ )
+
+
+class TestAsyncSession:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_create(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.create()
+ assert_matches_type(SessionCreateResponse, session, path=["response"])
+
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.create(
+ agent={},
+ id={},
+ location={},
+ metadata={},
+ model={},
+ parent_id={},
+ permissions={},
+ title="title",
+ )
+ assert_matches_type(SessionCreateResponse, session, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.create()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert_matches_type(SessionCreateResponse, session, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.create() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert_matches_type(SessionCreateResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_update(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.update(
+ session_id={},
+ )
+ assert session is None
+
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.update(
+ session_id={},
+ archived=True,
+ metadata={},
+ permissions={},
+ title="title",
+ )
+ assert session is None
+
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.update(
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert session is None
+
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.update(
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.update(
+ session_id="",
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.list()
+ assert_matches_type(SessionInfo, session, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.list(
+ archived=True,
+ cursor={},
+ directory={},
+ limit=0,
+ order="asc",
+ parent_id={},
+ project={},
+ search="search",
+ subpath={},
+ )
+ assert_matches_type(SessionInfo, session, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert_matches_type(SessionInfo, session, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert_matches_type(SessionInfo, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_active(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.active()
+ assert_matches_type(SessionActiveResponse, session, path=["response"])
+
+ @parametrize
+ async def test_raw_response_active(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.active()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert_matches_type(SessionActiveResponse, session, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_active(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.active() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert_matches_type(SessionActiveResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_background(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.background(
+ {},
+ )
+ assert session is None
+
+ @parametrize
+ async def test_raw_response_background(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.background(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert session is None
+
+ @parametrize
+ async def test_streaming_response_background(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.background(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_background(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.background(
+ "",
+ )
+
+ @parametrize
+ async def test_method_command(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.command(
+ session_id={},
+ name="name",
+ text="text",
+ )
+ assert session is None
+
+ @parametrize
+ async def test_method_command_with_all_params(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.command(
+ session_id={},
+ name="name",
+ text="text",
+ agents=[{}],
+ delivery={},
+ files=[{}],
+ skills=[{}],
+ )
+ assert session is None
+
+ @parametrize
+ async def test_raw_response_command(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.command(
+ session_id={},
+ name="name",
+ text="text",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert session is None
+
+ @parametrize
+ async def test_streaming_response_command(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.command(
+ session_id={},
+ name="name",
+ text="text",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_command(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.command(
+ session_id="",
+ name="name",
+ text="text",
+ )
+
+ @parametrize
+ async def test_method_compact(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.compact(
+ session_id={},
+ )
+ assert_matches_type(SessionCompactResponse, session, path=["response"])
+
+ @parametrize
+ async def test_method_compact_with_all_params(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.compact(
+ session_id={},
+ delivery={},
+ id={},
+ )
+ assert_matches_type(SessionCompactResponse, session, path=["response"])
+
+ @parametrize
+ async def test_raw_response_compact(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.compact(
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert_matches_type(SessionCompactResponse, session, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_compact(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.compact(
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert_matches_type(SessionCompactResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_compact(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.compact(
+ session_id="",
+ )
+
+ @parametrize
+ async def test_method_context(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.context(
+ {},
+ )
+ assert_matches_type(SessionContextResponse, session, path=["response"])
+
+ @parametrize
+ async def test_raw_response_context(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.context(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert_matches_type(SessionContextResponse, session, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_context(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.context(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert_matches_type(SessionContextResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_context(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.context(
+ "",
+ )
+
+ @parametrize
+ async def test_method_diff(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.diff(
+ session_id={},
+ )
+ assert_matches_type(SessionDiffResponse, session, path=["response"])
+
+ @parametrize
+ async def test_method_diff_with_all_params(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.diff(
+ session_id={},
+ context=0,
+ from_={},
+ to={},
+ )
+ assert_matches_type(SessionDiffResponse, session, path=["response"])
+
+ @parametrize
+ async def test_raw_response_diff(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.diff(
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert_matches_type(SessionDiffResponse, session, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_diff(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.diff(
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert_matches_type(SessionDiffResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_diff(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.diff(
+ session_id="",
+ )
+
+ @parametrize
+ async def test_method_environment(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.environment(
+ session_id={},
+ variables={"foo": "string"},
+ )
+ assert session is None
+
+ @parametrize
+ async def test_raw_response_environment(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.environment(
+ session_id={},
+ variables={"foo": "string"},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert session is None
+
+ @parametrize
+ async def test_streaming_response_environment(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.environment(
+ session_id={},
+ variables={"foo": "string"},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_environment(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.environment(
+ session_id="",
+ variables={"foo": "string"},
+ )
+
+ @parametrize
+ async def test_method_export(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.export(
+ session_id={},
+ )
+ assert_matches_type(SessionExportResponse, session, path=["response"])
+
+ @parametrize
+ async def test_method_export_with_all_params(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.export(
+ session_id={},
+ sanitize=True,
+ )
+ assert_matches_type(SessionExportResponse, session, path=["response"])
+
+ @parametrize
+ async def test_raw_response_export(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.export(
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert_matches_type(SessionExportResponse, session, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_export(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.export(
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert_matches_type(SessionExportResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_export(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.export(
+ session_id="",
+ )
+
+ @parametrize
+ async def test_method_fork(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.fork(
+ session_id={},
+ )
+ assert_matches_type(SessionForkResponse, session, path=["response"])
+
+ @parametrize
+ async def test_method_fork_with_all_params(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.fork(
+ session_id={},
+ before={},
+ )
+ assert_matches_type(SessionForkResponse, session, path=["response"])
+
+ @parametrize
+ async def test_raw_response_fork(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.fork(
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert_matches_type(SessionForkResponse, session, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_fork(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.fork(
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert_matches_type(SessionForkResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_fork(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.fork(
+ session_id="",
+ )
+
+ @parametrize
+ async def test_method_generate(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.generate(
+ session_id={},
+ prompt="prompt",
+ )
+ assert_matches_type(SessionGenerateResponse, session, path=["response"])
+
+ @parametrize
+ async def test_raw_response_generate(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.generate(
+ session_id={},
+ prompt="prompt",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert_matches_type(SessionGenerateResponse, session, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_generate(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.generate(
+ session_id={},
+ prompt="prompt",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert_matches_type(SessionGenerateResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_generate(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.generate(
+ session_id="",
+ prompt="prompt",
+ )
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.get(
+ {},
+ )
+ assert_matches_type(SessionGetResponse, session, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.get(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert_matches_type(SessionGetResponse, session, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.get(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert_matches_type(SessionGetResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_get(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.get(
+ "",
+ )
+
+ @parametrize
+ async def test_method_import_(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.import_(
+ info={},
+ messages=[{}],
+ )
+ assert_matches_type(SessionImportResponse, session, path=["response"])
+
+ @parametrize
+ async def test_method_import__with_all_params(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.import_(
+ info={},
+ messages=[{}],
+ location={},
+ )
+ assert_matches_type(SessionImportResponse, session, path=["response"])
+
+ @parametrize
+ async def test_raw_response_import_(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.import_(
+ info={},
+ messages=[{}],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert_matches_type(SessionImportResponse, session, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_import_(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.import_(
+ info={},
+ messages=[{}],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert_matches_type(SessionImportResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_interrupt(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.interrupt(
+ session_id={},
+ )
+ assert_matches_type(SessionInterruptResponse, session, path=["response"])
+
+ @parametrize
+ async def test_method_interrupt_with_all_params(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.interrupt(
+ session_id={},
+ resume=True,
+ )
+ assert_matches_type(SessionInterruptResponse, session, path=["response"])
+
+ @parametrize
+ async def test_raw_response_interrupt(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.interrupt(
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert_matches_type(SessionInterruptResponse, session, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_interrupt(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.interrupt(
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert_matches_type(SessionInterruptResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_interrupt(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.interrupt(
+ session_id="",
+ )
+
+ @parametrize
+ async def test_method_log(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.log(
+ session_id={},
+ )
+ assert session is None
+
+ @parametrize
+ async def test_method_log_with_all_params(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.log(
+ session_id={},
+ after={},
+ follow=True,
+ )
+ assert session is None
+
+ @parametrize
+ async def test_raw_response_log(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.log(
+ session_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert session is None
+
+ @parametrize
+ async def test_streaming_response_log(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.log(
+ session_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_log(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.log(
+ session_id="",
+ )
+
+ @parametrize
+ async def test_method_move(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.move(
+ session_id={},
+ directory={},
+ )
+ assert session is None
+
+ @parametrize
+ async def test_method_move_with_all_params(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.move(
+ session_id={},
+ directory={},
+ delivery={},
+ )
+ assert session is None
+
+ @parametrize
+ async def test_raw_response_move(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.move(
+ session_id={},
+ directory={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert session is None
+
+ @parametrize
+ async def test_streaming_response_move(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.move(
+ session_id={},
+ directory={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_move(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.move(
+ session_id="",
+ directory={},
+ )
+
+ @parametrize
+ async def test_method_prompt(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.prompt(
+ session_id={},
+ text="text",
+ )
+ assert_matches_type(SessionPromptResponse, session, path=["response"])
+
+ @parametrize
+ async def test_method_prompt_with_all_params(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.prompt(
+ session_id={},
+ text="text",
+ agents=[{}],
+ delivery={},
+ files=[{}],
+ id={},
+ metadata={"foo": {}},
+ resume=True,
+ skills=[{}],
+ )
+ assert_matches_type(SessionPromptResponse, session, path=["response"])
+
+ @parametrize
+ async def test_raw_response_prompt(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.prompt(
+ session_id={},
+ text="text",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert_matches_type(SessionPromptResponse, session, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_prompt(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.prompt(
+ session_id={},
+ text="text",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert_matches_type(SessionPromptResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_prompt(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.prompt(
+ session_id="",
+ text="text",
+ )
+
+ @parametrize
+ async def test_method_remove(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.remove(
+ {},
+ )
+ assert session is None
+
+ @parametrize
+ async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.remove(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert session is None
+
+ @parametrize
+ async def test_streaming_response_remove(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.remove(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_remove(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.remove(
+ "",
+ )
+
+ @parametrize
+ async def test_method_shell(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.shell(
+ session_id={},
+ command="command",
+ )
+ assert session is None
+
+ @parametrize
+ async def test_method_shell_with_all_params(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.shell(
+ session_id={},
+ command="command",
+ id={},
+ )
+ assert session is None
+
+ @parametrize
+ async def test_raw_response_shell(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.shell(
+ session_id={},
+ command="command",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert session is None
+
+ @parametrize
+ async def test_streaming_response_shell(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.shell(
+ session_id={},
+ command="command",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_shell(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.shell(
+ session_id="",
+ command="command",
+ )
+
+ @parametrize
+ async def test_method_skill(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.skill(
+ session_id={},
+ id={},
+ )
+ assert session is None
+
+ @parametrize
+ async def test_method_skill_with_all_params(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.skill(
+ session_id={},
+ id={},
+ resume=True,
+ )
+ assert session is None
+
+ @parametrize
+ async def test_raw_response_skill(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.skill(
+ session_id={},
+ id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert session is None
+
+ @parametrize
+ async def test_streaming_response_skill(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.skill(
+ session_id={},
+ id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_skill(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.skill(
+ session_id="",
+ id={},
+ )
+
+ @parametrize
+ async def test_method_stats(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.stats()
+ assert_matches_type(SessionStatsResponse, session, path=["response"])
+
+ @parametrize
+ async def test_method_stats_with_all_params(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.stats(
+ from_=0,
+ project={},
+ timezone="timezone",
+ to=0,
+ tools="none",
+ )
+ assert_matches_type(SessionStatsResponse, session, path=["response"])
+
+ @parametrize
+ async def test_raw_response_stats(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.stats()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert_matches_type(SessionStatsResponse, session, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_stats(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.stats() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert_matches_type(SessionStatsResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_switch_agent(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.switch_agent(
+ session_id={},
+ agent={},
+ )
+ assert session is None
+
+ @parametrize
+ async def test_raw_response_switch_agent(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.switch_agent(
+ session_id={},
+ agent={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert session is None
+
+ @parametrize
+ async def test_streaming_response_switch_agent(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.switch_agent(
+ session_id={},
+ agent={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_switch_agent(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.switch_agent(
+ session_id="",
+ agent={},
+ )
+
+ @parametrize
+ async def test_method_switch_model(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.switch_model(
+ session_id={},
+ model={},
+ )
+ assert session is None
+
+ @parametrize
+ async def test_raw_response_switch_model(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.switch_model(
+ session_id={},
+ model={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert session is None
+
+ @parametrize
+ async def test_streaming_response_switch_model(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.switch_model(
+ session_id={},
+ model={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_switch_model(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.switch_model(
+ session_id="",
+ model={},
+ )
+
+ @parametrize
+ async def test_method_synthetic(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.synthetic(
+ session_id={},
+ text="text",
+ )
+ assert_matches_type(SessionSyntheticResponse, session, path=["response"])
+
+ @parametrize
+ async def test_method_synthetic_with_all_params(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.synthetic(
+ session_id={},
+ text="text",
+ delivery={},
+ description="description",
+ id={},
+ metadata={"foo": {}},
+ resume=True,
+ )
+ assert_matches_type(SessionSyntheticResponse, session, path=["response"])
+
+ @parametrize
+ async def test_raw_response_synthetic(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.synthetic(
+ session_id={},
+ text="text",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert_matches_type(SessionSyntheticResponse, session, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_synthetic(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.synthetic(
+ session_id={},
+ text="text",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert_matches_type(SessionSyntheticResponse, session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_synthetic(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.synthetic(
+ session_id="",
+ text="text",
+ )
+
+ @parametrize
+ async def test_method_view(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.view(
+ session_id={},
+ idle=0,
+ )
+ assert session is None
+
+ @parametrize
+ async def test_raw_response_view(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.view(
+ session_id={},
+ idle=0,
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert session is None
+
+ @parametrize
+ async def test_streaming_response_view(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.view(
+ session_id={},
+ idle=0,
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_view(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.view(
+ session_id="",
+ idle=0,
+ )
+
+ @parametrize
+ async def test_method_wait(self, async_client: AsyncOpencode) -> None:
+ session = await async_client.session.wait(
+ {},
+ )
+ assert session is None
+
+ @parametrize
+ async def test_raw_response_wait(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.session.with_raw_response.wait(
+ {},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ session = await response.parse()
+ assert session is None
+
+ @parametrize
+ async def test_streaming_response_wait(self, async_client: AsyncOpencode) -> None:
+ async with async_client.session.with_streaming_response.wait(
+ {},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ session = await response.parse()
+ assert session is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_wait(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
+ await async_client.session.with_raw_response.wait(
+ "",
+ )
diff --git a/tests/api_resources/test_shell.py b/tests/api_resources/test_shell.py
new file mode 100644
index 0000000..c8efba8
--- /dev/null
+++ b/tests/api_resources/test_shell.py
@@ -0,0 +1,477 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import (
+ ShellGetResponse,
+ ShellListResponse,
+ ShellCreateResponse,
+ ShellOutputResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestShell:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_create(self, client: Opencode) -> None:
+ shell = client.shell.create(
+ command="command",
+ )
+ assert_matches_type(ShellCreateResponse, shell, path=["response"])
+
+ @parametrize
+ def test_method_create_with_all_params(self, client: Opencode) -> None:
+ shell = client.shell.create(
+ command="command",
+ api_timeout=0,
+ cwd="cwd",
+ location={
+ "directory": "string",
+ },
+ metadata={"foo": {}},
+ )
+ assert_matches_type(ShellCreateResponse, shell, path=["response"])
+
+ @parametrize
+ def test_raw_response_create(self, client: Opencode) -> None:
+ response = client.shell.with_raw_response.create(
+ command="command",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ shell = response.parse()
+ assert_matches_type(ShellCreateResponse, shell, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create(self, client: Opencode) -> None:
+ with client.shell.with_streaming_response.create(
+ command="command",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ shell = response.parse()
+ assert_matches_type(ShellCreateResponse, shell, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ shell = client.shell.list()
+ assert_matches_type(ShellListResponse, shell, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ shell = client.shell.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ShellListResponse, shell, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.shell.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ shell = response.parse()
+ assert_matches_type(ShellListResponse, shell, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.shell.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ shell = response.parse()
+ assert_matches_type(ShellListResponse, shell, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_get(self, client: Opencode) -> None:
+ shell = client.shell.get(
+ id={},
+ )
+ assert_matches_type(ShellGetResponse, shell, path=["response"])
+
+ @parametrize
+ def test_method_get_with_all_params(self, client: Opencode) -> None:
+ shell = client.shell.get(
+ id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ShellGetResponse, shell, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Opencode) -> None:
+ response = client.shell.with_raw_response.get(
+ id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ shell = response.parse()
+ assert_matches_type(ShellGetResponse, shell, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Opencode) -> None:
+ with client.shell.with_streaming_response.get(
+ id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ shell = response.parse()
+ assert_matches_type(ShellGetResponse, shell, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_get(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.shell.with_raw_response.get(
+ id="",
+ )
+
+ @parametrize
+ def test_method_output(self, client: Opencode) -> None:
+ shell = client.shell.output(
+ id={},
+ )
+ assert_matches_type(ShellOutputResponse, shell, path=["response"])
+
+ @parametrize
+ def test_method_output_with_all_params(self, client: Opencode) -> None:
+ shell = client.shell.output(
+ id={},
+ cursor=0,
+ limit=0,
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ShellOutputResponse, shell, path=["response"])
+
+ @parametrize
+ def test_raw_response_output(self, client: Opencode) -> None:
+ response = client.shell.with_raw_response.output(
+ id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ shell = response.parse()
+ assert_matches_type(ShellOutputResponse, shell, path=["response"])
+
+ @parametrize
+ def test_streaming_response_output(self, client: Opencode) -> None:
+ with client.shell.with_streaming_response.output(
+ id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ shell = response.parse()
+ assert_matches_type(ShellOutputResponse, shell, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_output(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.shell.with_raw_response.output(
+ id="",
+ )
+
+ @parametrize
+ def test_method_remove(self, client: Opencode) -> None:
+ shell = client.shell.remove(
+ id={},
+ )
+ assert shell is None
+
+ @parametrize
+ def test_method_remove_with_all_params(self, client: Opencode) -> None:
+ shell = client.shell.remove(
+ id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert shell is None
+
+ @parametrize
+ def test_raw_response_remove(self, client: Opencode) -> None:
+ response = client.shell.with_raw_response.remove(
+ id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ shell = response.parse()
+ assert shell is None
+
+ @parametrize
+ def test_streaming_response_remove(self, client: Opencode) -> None:
+ with client.shell.with_streaming_response.remove(
+ id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ shell = response.parse()
+ assert shell is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_remove(self, client: Opencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ client.shell.with_raw_response.remove(
+ id="",
+ )
+
+
+class TestAsyncShell:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_create(self, async_client: AsyncOpencode) -> None:
+ shell = await async_client.shell.create(
+ command="command",
+ )
+ assert_matches_type(ShellCreateResponse, shell, path=["response"])
+
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
+ shell = await async_client.shell.create(
+ command="command",
+ api_timeout=0,
+ cwd="cwd",
+ location={
+ "directory": "string",
+ },
+ metadata={"foo": {}},
+ )
+ assert_matches_type(ShellCreateResponse, shell, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.shell.with_raw_response.create(
+ command="command",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ shell = await response.parse()
+ assert_matches_type(ShellCreateResponse, shell, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
+ async with async_client.shell.with_streaming_response.create(
+ command="command",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ shell = await response.parse()
+ assert_matches_type(ShellCreateResponse, shell, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ shell = await async_client.shell.list()
+ assert_matches_type(ShellListResponse, shell, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ shell = await async_client.shell.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ShellListResponse, shell, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.shell.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ shell = await response.parse()
+ assert_matches_type(ShellListResponse, shell, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.shell.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ shell = await response.parse()
+ assert_matches_type(ShellListResponse, shell, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncOpencode) -> None:
+ shell = await async_client.shell.get(
+ id={},
+ )
+ assert_matches_type(ShellGetResponse, shell, path=["response"])
+
+ @parametrize
+ async def test_method_get_with_all_params(self, async_client: AsyncOpencode) -> None:
+ shell = await async_client.shell.get(
+ id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ShellGetResponse, shell, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.shell.with_raw_response.get(
+ id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ shell = await response.parse()
+ assert_matches_type(ShellGetResponse, shell, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
+ async with async_client.shell.with_streaming_response.get(
+ id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ shell = await response.parse()
+ assert_matches_type(ShellGetResponse, shell, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_get(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.shell.with_raw_response.get(
+ id="",
+ )
+
+ @parametrize
+ async def test_method_output(self, async_client: AsyncOpencode) -> None:
+ shell = await async_client.shell.output(
+ id={},
+ )
+ assert_matches_type(ShellOutputResponse, shell, path=["response"])
+
+ @parametrize
+ async def test_method_output_with_all_params(self, async_client: AsyncOpencode) -> None:
+ shell = await async_client.shell.output(
+ id={},
+ cursor=0,
+ limit=0,
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(ShellOutputResponse, shell, path=["response"])
+
+ @parametrize
+ async def test_raw_response_output(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.shell.with_raw_response.output(
+ id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ shell = await response.parse()
+ assert_matches_type(ShellOutputResponse, shell, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_output(self, async_client: AsyncOpencode) -> None:
+ async with async_client.shell.with_streaming_response.output(
+ id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ shell = await response.parse()
+ assert_matches_type(ShellOutputResponse, shell, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_output(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.shell.with_raw_response.output(
+ id="",
+ )
+
+ @parametrize
+ async def test_method_remove(self, async_client: AsyncOpencode) -> None:
+ shell = await async_client.shell.remove(
+ id={},
+ )
+ assert shell is None
+
+ @parametrize
+ async def test_method_remove_with_all_params(self, async_client: AsyncOpencode) -> None:
+ shell = await async_client.shell.remove(
+ id={},
+ location={
+ "directory": "string",
+ },
+ )
+ assert shell is None
+
+ @parametrize
+ async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.shell.with_raw_response.remove(
+ id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ shell = await response.parse()
+ assert shell is None
+
+ @parametrize
+ async def test_streaming_response_remove(self, async_client: AsyncOpencode) -> None:
+ async with async_client.shell.with_streaming_response.remove(
+ id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ shell = await response.parse()
+ assert shell is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_remove(self, async_client: AsyncOpencode) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
+ await async_client.shell.with_raw_response.remove(
+ id="",
+ )
diff --git a/tests/api_resources/test_skill.py b/tests/api_resources/test_skill.py
new file mode 100644
index 0000000..c3f63e8
--- /dev/null
+++ b/tests/api_resources/test_skill.py
@@ -0,0 +1,92 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import SkillListResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestSkill:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ skill = client.skill.list()
+ assert_matches_type(SkillListResponse, skill, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ skill = client.skill.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(SkillListResponse, skill, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.skill.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ skill = response.parse()
+ assert_matches_type(SkillListResponse, skill, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.skill.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ skill = response.parse()
+ assert_matches_type(SkillListResponse, skill, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncSkill:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ skill = await async_client.skill.list()
+ assert_matches_type(SkillListResponse, skill, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ skill = await async_client.skill.list(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(SkillListResponse, skill, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.skill.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ skill = await response.parse()
+ assert_matches_type(SkillListResponse, skill, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.skill.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ skill = await response.parse()
+ assert_matches_type(SkillListResponse, skill, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_vcs.py b/tests/api_resources/test_vcs.py
new file mode 100644
index 0000000..ee95198
--- /dev/null
+++ b/tests/api_resources/test_vcs.py
@@ -0,0 +1,389 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import (
+ VcGetResponse,
+ VcBaseResponse,
+ VcDiffResponse,
+ VcStatusResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestVcs:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_base(self, client: Opencode) -> None:
+ vc = client.vcs.base()
+ assert_matches_type(VcBaseResponse, vc, path=["response"])
+
+ @parametrize
+ def test_method_base_with_all_params(self, client: Opencode) -> None:
+ vc = client.vcs.base(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(VcBaseResponse, vc, path=["response"])
+
+ @parametrize
+ def test_raw_response_base(self, client: Opencode) -> None:
+ response = client.vcs.with_raw_response.base()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vc = response.parse()
+ assert_matches_type(VcBaseResponse, vc, path=["response"])
+
+ @parametrize
+ def test_streaming_response_base(self, client: Opencode) -> None:
+ with client.vcs.with_streaming_response.base() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vc = response.parse()
+ assert_matches_type(VcBaseResponse, vc, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_diff(self, client: Opencode) -> None:
+ vc = client.vcs.diff(
+ mode={},
+ )
+ assert_matches_type(VcDiffResponse, vc, path=["response"])
+
+ @parametrize
+ def test_method_diff_with_all_params(self, client: Opencode) -> None:
+ vc = client.vcs.diff(
+ mode={},
+ base="base",
+ context=0,
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(VcDiffResponse, vc, path=["response"])
+
+ @parametrize
+ def test_raw_response_diff(self, client: Opencode) -> None:
+ response = client.vcs.with_raw_response.diff(
+ mode={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vc = response.parse()
+ assert_matches_type(VcDiffResponse, vc, path=["response"])
+
+ @parametrize
+ def test_streaming_response_diff(self, client: Opencode) -> None:
+ with client.vcs.with_streaming_response.diff(
+ mode={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vc = response.parse()
+ assert_matches_type(VcDiffResponse, vc, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_get(self, client: Opencode) -> None:
+ vc = client.vcs.get()
+ assert_matches_type(VcGetResponse, vc, path=["response"])
+
+ @parametrize
+ def test_method_get_with_all_params(self, client: Opencode) -> None:
+ vc = client.vcs.get(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(VcGetResponse, vc, path=["response"])
+
+ @parametrize
+ def test_raw_response_get(self, client: Opencode) -> None:
+ response = client.vcs.with_raw_response.get()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vc = response.parse()
+ assert_matches_type(VcGetResponse, vc, path=["response"])
+
+ @parametrize
+ def test_streaming_response_get(self, client: Opencode) -> None:
+ with client.vcs.with_streaming_response.get() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vc = response.parse()
+ assert_matches_type(VcGetResponse, vc, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_init(self, client: Opencode) -> None:
+ vc = client.vcs.init()
+ assert vc is None
+
+ @parametrize
+ def test_method_init_with_all_params(self, client: Opencode) -> None:
+ vc = client.vcs.init(
+ location={
+ "directory": "string",
+ },
+ provider="provider",
+ )
+ assert vc is None
+
+ @parametrize
+ def test_raw_response_init(self, client: Opencode) -> None:
+ response = client.vcs.with_raw_response.init()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vc = response.parse()
+ assert vc is None
+
+ @parametrize
+ def test_streaming_response_init(self, client: Opencode) -> None:
+ with client.vcs.with_streaming_response.init() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vc = response.parse()
+ assert vc is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_status(self, client: Opencode) -> None:
+ vc = client.vcs.status()
+ assert_matches_type(VcStatusResponse, vc, path=["response"])
+
+ @parametrize
+ def test_method_status_with_all_params(self, client: Opencode) -> None:
+ vc = client.vcs.status(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(VcStatusResponse, vc, path=["response"])
+
+ @parametrize
+ def test_raw_response_status(self, client: Opencode) -> None:
+ response = client.vcs.with_raw_response.status()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vc = response.parse()
+ assert_matches_type(VcStatusResponse, vc, path=["response"])
+
+ @parametrize
+ def test_streaming_response_status(self, client: Opencode) -> None:
+ with client.vcs.with_streaming_response.status() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vc = response.parse()
+ assert_matches_type(VcStatusResponse, vc, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncVcs:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_base(self, async_client: AsyncOpencode) -> None:
+ vc = await async_client.vcs.base()
+ assert_matches_type(VcBaseResponse, vc, path=["response"])
+
+ @parametrize
+ async def test_method_base_with_all_params(self, async_client: AsyncOpencode) -> None:
+ vc = await async_client.vcs.base(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(VcBaseResponse, vc, path=["response"])
+
+ @parametrize
+ async def test_raw_response_base(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.vcs.with_raw_response.base()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vc = await response.parse()
+ assert_matches_type(VcBaseResponse, vc, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_base(self, async_client: AsyncOpencode) -> None:
+ async with async_client.vcs.with_streaming_response.base() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vc = await response.parse()
+ assert_matches_type(VcBaseResponse, vc, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_diff(self, async_client: AsyncOpencode) -> None:
+ vc = await async_client.vcs.diff(
+ mode={},
+ )
+ assert_matches_type(VcDiffResponse, vc, path=["response"])
+
+ @parametrize
+ async def test_method_diff_with_all_params(self, async_client: AsyncOpencode) -> None:
+ vc = await async_client.vcs.diff(
+ mode={},
+ base="base",
+ context=0,
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(VcDiffResponse, vc, path=["response"])
+
+ @parametrize
+ async def test_raw_response_diff(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.vcs.with_raw_response.diff(
+ mode={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vc = await response.parse()
+ assert_matches_type(VcDiffResponse, vc, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_diff(self, async_client: AsyncOpencode) -> None:
+ async with async_client.vcs.with_streaming_response.diff(
+ mode={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vc = await response.parse()
+ assert_matches_type(VcDiffResponse, vc, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_get(self, async_client: AsyncOpencode) -> None:
+ vc = await async_client.vcs.get()
+ assert_matches_type(VcGetResponse, vc, path=["response"])
+
+ @parametrize
+ async def test_method_get_with_all_params(self, async_client: AsyncOpencode) -> None:
+ vc = await async_client.vcs.get(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(VcGetResponse, vc, path=["response"])
+
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.vcs.with_raw_response.get()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vc = await response.parse()
+ assert_matches_type(VcGetResponse, vc, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
+ async with async_client.vcs.with_streaming_response.get() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vc = await response.parse()
+ assert_matches_type(VcGetResponse, vc, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_init(self, async_client: AsyncOpencode) -> None:
+ vc = await async_client.vcs.init()
+ assert vc is None
+
+ @parametrize
+ async def test_method_init_with_all_params(self, async_client: AsyncOpencode) -> None:
+ vc = await async_client.vcs.init(
+ location={
+ "directory": "string",
+ },
+ provider="provider",
+ )
+ assert vc is None
+
+ @parametrize
+ async def test_raw_response_init(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.vcs.with_raw_response.init()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vc = await response.parse()
+ assert vc is None
+
+ @parametrize
+ async def test_streaming_response_init(self, async_client: AsyncOpencode) -> None:
+ async with async_client.vcs.with_streaming_response.init() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vc = await response.parse()
+ assert vc is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_status(self, async_client: AsyncOpencode) -> None:
+ vc = await async_client.vcs.status()
+ assert_matches_type(VcStatusResponse, vc, path=["response"])
+
+ @parametrize
+ async def test_method_status_with_all_params(self, async_client: AsyncOpencode) -> None:
+ vc = await async_client.vcs.status(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(VcStatusResponse, vc, path=["response"])
+
+ @parametrize
+ async def test_raw_response_status(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.vcs.with_raw_response.status()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vc = await response.parse()
+ assert_matches_type(VcStatusResponse, vc, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_status(self, async_client: AsyncOpencode) -> None:
+ async with async_client.vcs.with_streaming_response.status() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vc = await response.parse()
+ assert_matches_type(VcStatusResponse, vc, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_voice.py b/tests/api_resources/test_voice.py
new file mode 100644
index 0000000..74a55ed
--- /dev/null
+++ b/tests/api_resources/test_voice.py
@@ -0,0 +1,110 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.shared import VoiceSessionResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestVoice:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_session(self, client: Opencode) -> None:
+ voice = client.voice.session(
+ model="model",
+ transport={},
+ )
+ assert_matches_type(VoiceSessionResponse, voice, path=["response"])
+
+ @parametrize
+ def test_method_session_with_all_params(self, client: Opencode) -> None:
+ voice = client.voice.session(
+ model="model",
+ transport={},
+ session={"foo": {}},
+ )
+ assert_matches_type(VoiceSessionResponse, voice, path=["response"])
+
+ @parametrize
+ def test_raw_response_session(self, client: Opencode) -> None:
+ response = client.voice.with_raw_response.session(
+ model="model",
+ transport={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ voice = response.parse()
+ assert_matches_type(VoiceSessionResponse, voice, path=["response"])
+
+ @parametrize
+ def test_streaming_response_session(self, client: Opencode) -> None:
+ with client.voice.with_streaming_response.session(
+ model="model",
+ transport={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ voice = response.parse()
+ assert_matches_type(VoiceSessionResponse, voice, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncVoice:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_session(self, async_client: AsyncOpencode) -> None:
+ voice = await async_client.voice.session(
+ model="model",
+ transport={},
+ )
+ assert_matches_type(VoiceSessionResponse, voice, path=["response"])
+
+ @parametrize
+ async def test_method_session_with_all_params(self, async_client: AsyncOpencode) -> None:
+ voice = await async_client.voice.session(
+ model="model",
+ transport={},
+ session={"foo": {}},
+ )
+ assert_matches_type(VoiceSessionResponse, voice, path=["response"])
+
+ @parametrize
+ async def test_raw_response_session(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.voice.with_raw_response.session(
+ model="model",
+ transport={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ voice = await response.parse()
+ assert_matches_type(VoiceSessionResponse, voice, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_session(self, async_client: AsyncOpencode) -> None:
+ async with async_client.voice.with_streaming_response.session(
+ model="model",
+ transport={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ voice = await response.parse()
+ assert_matches_type(VoiceSessionResponse, voice, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_websearch.py b/tests/api_resources/test_websearch.py
new file mode 100644
index 0000000..26f9128
--- /dev/null
+++ b/tests/api_resources/test_websearch.py
@@ -0,0 +1,176 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types import WebsearchQueryResponse, WebsearchProvidersResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestWebsearch:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_providers(self, client: Opencode) -> None:
+ websearch = client.websearch.providers()
+ assert_matches_type(WebsearchProvidersResponse, websearch, path=["response"])
+
+ @parametrize
+ def test_method_providers_with_all_params(self, client: Opencode) -> None:
+ websearch = client.websearch.providers(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(WebsearchProvidersResponse, websearch, path=["response"])
+
+ @parametrize
+ def test_raw_response_providers(self, client: Opencode) -> None:
+ response = client.websearch.with_raw_response.providers()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ websearch = response.parse()
+ assert_matches_type(WebsearchProvidersResponse, websearch, path=["response"])
+
+ @parametrize
+ def test_streaming_response_providers(self, client: Opencode) -> None:
+ with client.websearch.with_streaming_response.providers() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ websearch = response.parse()
+ assert_matches_type(WebsearchProvidersResponse, websearch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_query(self, client: Opencode) -> None:
+ websearch = client.websearch.query(
+ query="query",
+ )
+ assert_matches_type(WebsearchQueryResponse, websearch, path=["response"])
+
+ @parametrize
+ def test_method_query_with_all_params(self, client: Opencode) -> None:
+ websearch = client.websearch.query(
+ query="query",
+ location={
+ "directory": "string",
+ },
+ provider_id={},
+ )
+ assert_matches_type(WebsearchQueryResponse, websearch, path=["response"])
+
+ @parametrize
+ def test_raw_response_query(self, client: Opencode) -> None:
+ response = client.websearch.with_raw_response.query(
+ query="query",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ websearch = response.parse()
+ assert_matches_type(WebsearchQueryResponse, websearch, path=["response"])
+
+ @parametrize
+ def test_streaming_response_query(self, client: Opencode) -> None:
+ with client.websearch.with_streaming_response.query(
+ query="query",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ websearch = response.parse()
+ assert_matches_type(WebsearchQueryResponse, websearch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncWebsearch:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_providers(self, async_client: AsyncOpencode) -> None:
+ websearch = await async_client.websearch.providers()
+ assert_matches_type(WebsearchProvidersResponse, websearch, path=["response"])
+
+ @parametrize
+ async def test_method_providers_with_all_params(self, async_client: AsyncOpencode) -> None:
+ websearch = await async_client.websearch.providers(
+ location={
+ "directory": "string",
+ },
+ )
+ assert_matches_type(WebsearchProvidersResponse, websearch, path=["response"])
+
+ @parametrize
+ async def test_raw_response_providers(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.websearch.with_raw_response.providers()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ websearch = await response.parse()
+ assert_matches_type(WebsearchProvidersResponse, websearch, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_providers(self, async_client: AsyncOpencode) -> None:
+ async with async_client.websearch.with_streaming_response.providers() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ websearch = await response.parse()
+ assert_matches_type(WebsearchProvidersResponse, websearch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_query(self, async_client: AsyncOpencode) -> None:
+ websearch = await async_client.websearch.query(
+ query="query",
+ )
+ assert_matches_type(WebsearchQueryResponse, websearch, path=["response"])
+
+ @parametrize
+ async def test_method_query_with_all_params(self, async_client: AsyncOpencode) -> None:
+ websearch = await async_client.websearch.query(
+ query="query",
+ location={
+ "directory": "string",
+ },
+ provider_id={},
+ )
+ assert_matches_type(WebsearchQueryResponse, websearch, path=["response"])
+
+ @parametrize
+ async def test_raw_response_query(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.websearch.with_raw_response.query(
+ query="query",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ websearch = await response.parse()
+ assert_matches_type(WebsearchQueryResponse, websearch, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_query(self, async_client: AsyncOpencode) -> None:
+ async with async_client.websearch.with_streaming_response.query(
+ query="query",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ websearch = await response.parse()
+ assert_matches_type(WebsearchQueryResponse, websearch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_worktree.py b/tests/api_resources/test_worktree.py
new file mode 100644
index 0000000..365bf45
--- /dev/null
+++ b/tests/api_resources/test_worktree.py
@@ -0,0 +1,306 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.shared import WorktreeInfo, WorktreeList
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestWorktree:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_create(self, client: Opencode) -> None:
+ worktree = client.worktree.create(
+ project_id={},
+ )
+ assert_matches_type(WorktreeInfo, worktree, path=["response"])
+
+ @parametrize
+ def test_method_create_with_all_params(self, client: Opencode) -> None:
+ worktree = client.worktree.create(
+ project_id={},
+ branch="branch",
+ directory={},
+ from_={},
+ name="name",
+ )
+ assert_matches_type(WorktreeInfo, worktree, path=["response"])
+
+ @parametrize
+ def test_raw_response_create(self, client: Opencode) -> None:
+ response = client.worktree.with_raw_response.create(
+ project_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ worktree = response.parse()
+ assert_matches_type(WorktreeInfo, worktree, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create(self, client: Opencode) -> None:
+ with client.worktree.with_streaming_response.create(
+ project_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ worktree = response.parse()
+ assert_matches_type(WorktreeInfo, worktree, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ worktree = client.worktree.list(
+ project_id={},
+ )
+ assert_matches_type(WorktreeList, worktree, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.worktree.with_raw_response.list(
+ project_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ worktree = response.parse()
+ assert_matches_type(WorktreeList, worktree, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.worktree.with_streaming_response.list(
+ project_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ worktree = response.parse()
+ assert_matches_type(WorktreeList, worktree, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_refresh(self, client: Opencode) -> None:
+ worktree = client.worktree.refresh(
+ project_id={},
+ )
+ assert worktree is None
+
+ @parametrize
+ def test_raw_response_refresh(self, client: Opencode) -> None:
+ response = client.worktree.with_raw_response.refresh(
+ project_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ worktree = response.parse()
+ assert worktree is None
+
+ @parametrize
+ def test_streaming_response_refresh(self, client: Opencode) -> None:
+ with client.worktree.with_streaming_response.refresh(
+ project_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ worktree = response.parse()
+ assert worktree is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_remove(self, client: Opencode) -> None:
+ worktree = client.worktree.remove(
+ directory={},
+ force=True,
+ project_id={},
+ )
+ assert worktree is None
+
+ @parametrize
+ def test_raw_response_remove(self, client: Opencode) -> None:
+ response = client.worktree.with_raw_response.remove(
+ directory={},
+ force=True,
+ project_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ worktree = response.parse()
+ assert worktree is None
+
+ @parametrize
+ def test_streaming_response_remove(self, client: Opencode) -> None:
+ with client.worktree.with_streaming_response.remove(
+ directory={},
+ force=True,
+ project_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ worktree = response.parse()
+ assert worktree is None
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncWorktree:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_create(self, async_client: AsyncOpencode) -> None:
+ worktree = await async_client.worktree.create(
+ project_id={},
+ )
+ assert_matches_type(WorktreeInfo, worktree, path=["response"])
+
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
+ worktree = await async_client.worktree.create(
+ project_id={},
+ branch="branch",
+ directory={},
+ from_={},
+ name="name",
+ )
+ assert_matches_type(WorktreeInfo, worktree, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.worktree.with_raw_response.create(
+ project_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ worktree = await response.parse()
+ assert_matches_type(WorktreeInfo, worktree, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
+ async with async_client.worktree.with_streaming_response.create(
+ project_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ worktree = await response.parse()
+ assert_matches_type(WorktreeInfo, worktree, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ worktree = await async_client.worktree.list(
+ project_id={},
+ )
+ assert_matches_type(WorktreeList, worktree, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.worktree.with_raw_response.list(
+ project_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ worktree = await response.parse()
+ assert_matches_type(WorktreeList, worktree, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.worktree.with_streaming_response.list(
+ project_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ worktree = await response.parse()
+ assert_matches_type(WorktreeList, worktree, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_refresh(self, async_client: AsyncOpencode) -> None:
+ worktree = await async_client.worktree.refresh(
+ project_id={},
+ )
+ assert worktree is None
+
+ @parametrize
+ async def test_raw_response_refresh(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.worktree.with_raw_response.refresh(
+ project_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ worktree = await response.parse()
+ assert worktree is None
+
+ @parametrize
+ async def test_streaming_response_refresh(self, async_client: AsyncOpencode) -> None:
+ async with async_client.worktree.with_streaming_response.refresh(
+ project_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ worktree = await response.parse()
+ assert worktree is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_remove(self, async_client: AsyncOpencode) -> None:
+ worktree = await async_client.worktree.remove(
+ directory={},
+ force=True,
+ project_id={},
+ )
+ assert worktree is None
+
+ @parametrize
+ async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.worktree.with_raw_response.remove(
+ directory={},
+ force=True,
+ project_id={},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ worktree = await response.parse()
+ assert worktree is None
+
+ @parametrize
+ async def test_streaming_response_remove(self, async_client: AsyncOpencode) -> None:
+ async with async_client.worktree.with_streaming_response.remove(
+ directory={},
+ force=True,
+ project_id={},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ worktree = await response.parse()
+ assert worktree is None
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/vcs/__init__.py b/tests/api_resources/vcs/__init__.py
new file mode 100644
index 0000000..639750b
--- /dev/null
+++ b/tests/api_resources/vcs/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/vcs/test_branch.py b/tests/api_resources/vcs/test_branch.py
new file mode 100644
index 0000000..59acb10
--- /dev/null
+++ b/tests/api_resources/vcs/test_branch.py
@@ -0,0 +1,96 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from opencode import Opencode, AsyncOpencode
+from tests.utils import assert_matches_type
+from opencode.types.vcs import BranchListResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestBranch:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: Opencode) -> None:
+ branch = client.vcs.branch.list()
+ assert_matches_type(BranchListResponse, branch, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Opencode) -> None:
+ branch = client.vcs.branch.list(
+ limit=0,
+ location={
+ "directory": "string",
+ },
+ search="search",
+ )
+ assert_matches_type(BranchListResponse, branch, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Opencode) -> None:
+ response = client.vcs.branch.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ branch = response.parse()
+ assert_matches_type(BranchListResponse, branch, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Opencode) -> None:
+ with client.vcs.branch.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ branch = response.parse()
+ assert_matches_type(BranchListResponse, branch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncBranch:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOpencode) -> None:
+ branch = await async_client.vcs.branch.list()
+ assert_matches_type(BranchListResponse, branch, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
+ branch = await async_client.vcs.branch.list(
+ limit=0,
+ location={
+ "directory": "string",
+ },
+ search="search",
+ )
+ assert_matches_type(BranchListResponse, branch, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
+ response = await async_client.vcs.branch.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ branch = await response.parse()
+ assert_matches_type(BranchListResponse, branch, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
+ async with async_client.vcs.branch.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ branch = await response.parse()
+ assert_matches_type(BranchListResponse, branch, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/conftest.py b/tests/conftest.py
index ee9edc9..b260272 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,45 +1,91 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
from __future__ import annotations
-from collections.abc import AsyncIterator, Iterator
+import os
+import logging
+from typing import TYPE_CHECKING, Iterator, AsyncIterator
+import httpx
import pytest
-import respx
+from pytest_asyncio import is_async_test
+
+from opencode import Opencode, AsyncOpencode, DefaultAioHttpClient
+from opencode._utils import is_dict
+
+if TYPE_CHECKING:
+ from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage]
+
+pytest.register_assert_rewrite("tests.utils")
+
+logging.getLogger("opencode").setLevel(logging.DEBUG)
+
+
+# automatically add `pytest.mark.asyncio()` to all of our async tests
+# so we don't have to add that boilerplate everywhere
+def pytest_collection_modifyitems(items: list[pytest.Function]) -> None:
+ pytest_asyncio_tests = (item for item in items if is_async_test(item))
+ session_scope_marker = pytest.mark.asyncio(loop_scope="session")
+ for async_test in pytest_asyncio_tests:
+ async_test.add_marker(session_scope_marker, append=False)
+
+ # We skip tests that use both the aiohttp client and respx_mock as respx_mock
+ # doesn't support custom transports.
+ for item in items:
+ if "async_client" not in item.fixturenames or "respx_mock" not in item.fixturenames:
+ continue
-from opencode import AsyncOpencode, Opencode
+ if not hasattr(item, "callspec"):
+ continue
-BASE_URL = "http://opencode.test"
-PASSWORD = "test-password"
+ async_client_param = item.callspec.params.get("async_client")
+ if is_dict(async_client_param) and async_client_param.get("http_client") == "aiohttp":
+ item.add_marker(pytest.mark.skip(reason="aiohttp client is not compatible with respx_mock"))
-@pytest.fixture
-def anyio_backend() -> str:
- return "asyncio"
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+username = "My Username"
+password = "My Password"
-@pytest.fixture
-def mock_api() -> Iterator[respx.MockRouter]:
- with respx.mock(base_url=BASE_URL, assert_all_called=False) as router:
- yield router
+@pytest.fixture(scope="session")
+def client(request: FixtureRequest) -> Iterator[Opencode]:
+ strict = getattr(request, "param", True)
+ if not isinstance(strict, bool):
+ raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")
-@pytest.fixture
-def client(mock_api: respx.MockRouter) -> Iterator[Opencode]:
- with Opencode(BASE_URL, password=PASSWORD, max_retries=2) as instance:
- yield instance
+ with Opencode(
+ base_url=base_url, username=username, password=password, _strict_response_validation=strict
+ ) as client:
+ yield client
-@pytest.fixture
-async def async_client(mock_api: respx.MockRouter) -> AsyncIterator[AsyncOpencode]:
- async with AsyncOpencode(BASE_URL, password=PASSWORD, max_retries=2) as instance:
- yield instance
+@pytest.fixture(scope="session")
+async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncOpencode]:
+ param = getattr(request, "param", True)
+ # defaults
+ strict = True
+ http_client: None | httpx.AsyncClient = None
-@pytest.fixture(autouse=True)
-def _no_retry_sleep(monkeypatch: pytest.MonkeyPatch) -> None:
- """Retries back off with real sleeps; skip them so tests stay fast."""
+ if isinstance(param, bool):
+ strict = param
+ elif is_dict(param):
+ strict = param.get("strict", True)
+ assert isinstance(strict, bool)
- async def instant(_: float) -> None:
- return None
+ http_client_type = param.get("http_client", "httpx")
+ if http_client_type == "aiohttp":
+ http_client = DefaultAioHttpClient()
+ else:
+ raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict")
- monkeypatch.setattr("opencode._base_client.time.sleep", lambda _: None)
- monkeypatch.setattr("opencode._base_client.anyio.sleep", instant)
+ async with AsyncOpencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=strict,
+ http_client=http_client,
+ ) as client:
+ yield client
diff --git a/tests/sample_file.txt b/tests/sample_file.txt
new file mode 100644
index 0000000..af5626b
--- /dev/null
+++ b/tests/sample_file.txt
@@ -0,0 +1 @@
+Hello, world!
diff --git a/tests/test_agent_recipes.py b/tests/test_agent_recipes.py
new file mode 100644
index 0000000..8e7e5e1
--- /dev/null
+++ b/tests/test_agent_recipes.py
@@ -0,0 +1,23 @@
+# Generated by Ferrule: runs every examples/agent/ recipe against a mock server.
+
+from __future__ import annotations
+
+import os
+import runpy
+from pathlib import Path
+
+import httpx
+import pytest
+from respx import MockRouter
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+RECIPES = sorted((Path(__file__).parent.parent / "examples" / "agent").glob("*.py"))
+
+
+@pytest.mark.parametrize("recipe", RECIPES, ids=[p.stem for p in RECIPES])
+@pytest.mark.respx(base_url=base_url)
+def test_recipe_runs(recipe: Path, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None:
+ respx_mock.route().mock(return_value=httpx.Response(200, json={}))
+ monkeypatch.setenv("OPENCODE_PASSWORD", "My API Key")
+ monkeypatch.setenv("OPENCODE_BASE_URL", base_url)
+ runpy.run_path(str(recipe), run_name="__main__")
diff --git a/tests/test_client.py b/tests/test_client.py
index 048851a..56961c2 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -1,251 +1,2111 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
from __future__ import annotations
-import base64
+import gc
+import os
+import sys
import json
+import asyncio
+import inspect
+import dataclasses
+import tracemalloc
+from typing import Any, Union, TypeVar, Callable, Iterable, Iterator, Optional, Coroutine, cast
+from unittest import mock
+from typing_extensions import Literal, AsyncIterator, override
import httpx
import pytest
-import respx
-
-from opencode import (
- NOT_GIVEN,
- APIConnectionError,
- APIResponseValidationError,
- APITimeoutError,
- InternalServerError,
- NotFoundError,
- Opencode,
- OpencodeError,
- RateLimitError,
- SessionNotFoundError,
+from respx import MockRouter
+from pydantic import ValidationError
+
+from opencode import Opencode, AsyncOpencode, APIResponseValidationError
+from opencode._types import Omit
+from opencode._utils import asyncify
+from opencode._models import BaseModel, FinalRequestOptions
+from opencode._exceptions import OpencodeError, APIStatusError, APITimeoutError, APIResponseValidationError
+from opencode._base_client import (
+ DEFAULT_TIMEOUT,
+ HTTPX_DEFAULT_TIMEOUT,
+ BaseClient,
+ OtherPlatform,
+ DefaultHttpxClient,
+ DefaultAsyncHttpxClient,
+ get_platform,
+ make_request_options,
)
-from opencode.types import ModelRef, SessionMessageUser
-from .conftest import BASE_URL, PASSWORD
+from .utils import update_env
-SESSION = {
- "id": "ses_1",
- "projectID": "prj_1",
- "cost": 0,
- "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}},
- "time": {"created": 1, "updated": 1},
- "title": "Hello",
- "location": {"directory": "/repo"},
-}
-AGENTS: dict[str, object] = {"location": {"directory": "/repo"}, "data": []}
+T = TypeVar("T")
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+username = "My Username"
+password = "My Password"
-def _auth(username: str = "opencode", password: str = PASSWORD) -> str:
- return "Basic " + base64.b64encode(f"{username}:{password}".encode()).decode()
+def _get_params(client: BaseClient[Any, Any]) -> dict[str, str]:
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ url = httpx.URL(request.url)
+ return dict(url.params)
-def test_sends_basic_auth_and_user_agent(client: Opencode, mock_api: respx.MockRouter) -> None:
- route = mock_api.get("/api/agent").respond(json=AGENTS)
- client.agent.list()
- request = route.calls.last.request
- assert request.headers["Authorization"] == _auth()
- assert request.headers["User-Agent"].startswith("opencode-python/")
+def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float:
+ return 0.1
-def test_password_from_environment(monkeypatch: pytest.MonkeyPatch, mock_api: respx.MockRouter) -> None:
- monkeypatch.setenv("OPENCODE_PASSWORD", "from-env")
- route = mock_api.get("/api/agent").respond(json=AGENTS)
- with Opencode(BASE_URL) as client:
- client.agent.list()
- assert route.calls.last.request.headers["Authorization"] == _auth(password="from-env")
+def mirror_request_content(request: httpx.Request) -> httpx.Response:
+ return httpx.Response(200, content=request.content)
-def test_empty_password_disables_auth(monkeypatch: pytest.MonkeyPatch, mock_api: respx.MockRouter) -> None:
- monkeypatch.setenv("OPENCODE_PASSWORD", "from-env")
- route = mock_api.get("/api/agent").respond(json=AGENTS)
- with Opencode(BASE_URL, password="") as client:
- client.agent.list()
- assert "Authorization" not in route.calls.last.request.headers
+# note: we can't use the httpx.MockTransport class as it consumes the request
+# body itself, which means we can't test that the body is read lazily
+class MockTransport(httpx.BaseTransport, httpx.AsyncBaseTransport):
+ def __init__(
+ self,
+ handler: Callable[[httpx.Request], httpx.Response]
+ | Callable[[httpx.Request], Coroutine[Any, Any, httpx.Response]],
+ ) -> None:
+ self.handler = handler
+ @override
+ def handle_request(
+ self,
+ request: httpx.Request,
+ ) -> httpx.Response:
+ assert not inspect.iscoroutinefunction(self.handler), "handler must not be a coroutine function"
+ assert inspect.isfunction(self.handler), "handler must be a function"
+ return self.handler(request)
-def test_missing_base_url_explains_how_to_fix(monkeypatch: pytest.MonkeyPatch) -> None:
- monkeypatch.delenv("OPENCODE_BASE_URL", raising=False)
- with pytest.raises(OpencodeError, match="OPENCODE_BASE_URL"):
- Opencode()
+ @override
+ async def handle_async_request(
+ self,
+ request: httpx.Request,
+ ) -> httpx.Response:
+ assert inspect.iscoroutinefunction(self.handler), "handler must be a coroutine function"
+ return await self.handler(request)
-def test_repr_never_contains_password(client: Opencode) -> None:
- assert PASSWORD not in repr(client)
+@dataclasses.dataclass
+class Counter:
+ value: int = 0
-def test_base_url_path_prefix_is_kept() -> None:
- with respx.mock() as router:
- route = router.get("http://proxy.test/opencode/api/agent").respond(json=AGENTS)
- with Opencode("http://proxy.test/opencode", password="") as client:
- client.agent.list()
- assert route.called
+def _make_sync_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> Iterator[T]:
+ for item in iterable:
+ if counter:
+ counter.value += 1
+ yield item
-def test_body_omits_not_given_and_sends_explicit_null(client: Opencode, mock_api: respx.MockRouter) -> None:
- route = mock_api.post("/api/session").respond(json={"data": SESSION})
- session = client.session.create(title=None, model=ModelRef(id="m", provider_id="p"))
- assert session.id == "ses_1"
- assert json.loads(route.calls.last.request.content) == {"title": None, "model": {"id": "m", "providerID": "p"}}
+async def _make_async_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> AsyncIterator[T]:
+ for item in iterable:
+ if counter:
+ counter.value += 1
+ yield item
-def test_path_and_query_parameters(client: Opencode, mock_api: respx.MockRouter) -> None:
- route = mock_api.post("/api/session/ses%2F1/interrupt").respond(json={"interrupted": True})
- client.session.interrupt("ses/1", resume=False)
- assert route.calls.last.request.url.params["resume"] == "false"
+def _get_open_connections(client: Opencode | AsyncOpencode) -> int:
+ transport = client._client._transport
+ assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport)
+ pool = transport._pool
+ return len(pool._requests)
-def test_default_directory_becomes_location(mock_api: respx.MockRouter) -> None:
- route = mock_api.get("/api/agent").respond(json=AGENTS)
- with Opencode(BASE_URL, password="", directory="/repo") as client:
- client.agent.list()
- client.agent.list(location={"directory": "/other"}) # type: ignore[arg-type]
- assert route.calls[0].request.url.params["location[directory]"] == "/repo"
- assert route.calls[1].request.url.params["location[directory]"] == "/other"
+class TestOpencode:
+ @pytest.mark.respx(base_url=base_url)
+ def test_raw_response(self, respx_mock: MockRouter, client: Opencode) -> None:
+ respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
-def test_extra_options_are_merged(client: Opencode, mock_api: respx.MockRouter) -> None:
- route = mock_api.post("/api/session").respond(json={"data": SESSION})
- client.session.create(
- title="t", extra_headers={"X-Trace": "1"}, extra_query={"debug": True}, extra_body={"experimental": 1}
- )
- request = route.calls.last.request
- assert request.headers["X-Trace"] == "1"
- assert request.url.params["debug"] == "true"
- assert json.loads(request.content) == {"title": "t", "experimental": 1}
-
-
-def test_unwraps_data_envelope_into_model(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.post("/api/session/ses_1/prompt").respond(
- json={
- "data": {
- "id": "msg_1",
- "sessionID": "ses_1",
- "time": {"created": 1},
- "type": "user",
- "payload": {"text": "hi"},
- "delivery": "steer",
- }
- }
+ response = client.post("/foo", cast_to=httpx.Response)
+ assert response.status_code == 200
+ assert isinstance(response, httpx.Response)
+ assert response.json() == {"foo": "bar"}
+
+ @pytest.mark.respx(base_url=base_url)
+ def test_raw_response_for_binary(self, respx_mock: MockRouter, client: Opencode) -> None:
+ respx_mock.post("/foo").mock(
+ return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}')
+ )
+
+ response = client.post("/foo", cast_to=httpx.Response)
+ assert response.status_code == 200
+ assert isinstance(response, httpx.Response)
+ assert response.json() == {"foo": "bar"}
+
+ def test_copy(self, client: Opencode) -> None:
+ copied = client.copy()
+ assert id(copied) != id(client)
+
+ copied = client.copy(username="another My Username")
+ assert copied.username == "another My Username"
+ assert client.username == "My Username"
+
+ copied = client.copy(password="another My Password")
+ assert copied.password == "another My Password"
+ assert client.password == "My Password"
+
+ def test_copy_default_options(self, client: Opencode) -> None:
+ # options that have a default are overridden correctly
+ copied = client.copy(max_retries=7)
+ assert copied.max_retries == 7
+ assert client.max_retries == 2
+
+ copied2 = copied.copy(max_retries=6)
+ assert copied2.max_retries == 6
+ assert copied.max_retries == 7
+
+ # timeout
+ assert isinstance(client.timeout, httpx.Timeout)
+ copied = client.copy(timeout=None)
+ assert copied.timeout is None
+ assert isinstance(client.timeout, httpx.Timeout)
+
+ def test_copy_default_headers(self) -> None:
+ client = Opencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ default_headers={"X-Foo": "bar"},
+ )
+ assert client.default_headers["X-Foo"] == "bar"
+
+ # does not override the already given value when not specified
+ copied = client.copy()
+ assert copied.default_headers["X-Foo"] == "bar"
+
+ # merges already given headers
+ copied = client.copy(default_headers={"X-Bar": "stainless"})
+ assert copied.default_headers["X-Foo"] == "bar"
+ assert copied.default_headers["X-Bar"] == "stainless"
+
+ # uses new values for any already given headers
+ copied = client.copy(default_headers={"X-Foo": "stainless"})
+ assert copied.default_headers["X-Foo"] == "stainless"
+
+ # set_default_headers
+
+ # completely overrides already set values
+ copied = client.copy(set_default_headers={})
+ assert copied.default_headers.get("X-Foo") is None
+
+ copied = client.copy(set_default_headers={"X-Bar": "Robert"})
+ assert copied.default_headers["X-Bar"] == "Robert"
+
+ with pytest.raises(
+ ValueError,
+ match="`default_headers` and `set_default_headers` arguments are mutually exclusive",
+ ):
+ client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"})
+ client.close()
+
+ def test_copy_default_query(self) -> None:
+ client = Opencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ default_query={"foo": "bar"},
+ )
+ assert _get_params(client)["foo"] == "bar"
+
+ # does not override the already given value when not specified
+ copied = client.copy()
+ assert _get_params(copied)["foo"] == "bar"
+
+ # merges already given params
+ copied = client.copy(default_query={"bar": "stainless"})
+ params = _get_params(copied)
+ assert params["foo"] == "bar"
+ assert params["bar"] == "stainless"
+
+ # uses new values for any already given headers
+ copied = client.copy(default_query={"foo": "stainless"})
+ assert _get_params(copied)["foo"] == "stainless"
+
+ # set_default_query
+
+ # completely overrides already set values
+ copied = client.copy(set_default_query={})
+ assert _get_params(copied) == {}
+
+ copied = client.copy(set_default_query={"bar": "Robert"})
+ assert _get_params(copied)["bar"] == "Robert"
+
+ with pytest.raises(
+ ValueError,
+ # TODO: update
+ match="`default_query` and `set_default_query` arguments are mutually exclusive",
+ ):
+ client.copy(set_default_query={}, default_query={"foo": "Bar"})
+
+ client.close()
+
+ def test_copy_signature(self, client: Opencode) -> None:
+ # ensure the same parameters that can be passed to the client are defined in the `.copy()` method
+ init_signature = inspect.signature(
+ # mypy doesn't like that we access the `__init__` property.
+ client.__init__, # type: ignore[misc]
+ )
+ copy_signature = inspect.signature(client.copy)
+ exclude_params = {"transport", "proxies", "_strict_response_validation"}
+
+ for name in init_signature.parameters.keys():
+ if name in exclude_params:
+ continue
+
+ copy_param = copy_signature.parameters.get(name)
+ assert copy_param is not None, f"copy() signature is missing the {name} param"
+
+ @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12")
+ def test_copy_build_request(self, client: Opencode) -> None:
+ options = FinalRequestOptions(method="get", url="/foo")
+
+ def build_request(options: FinalRequestOptions) -> None:
+ client_copy = client.copy()
+ client_copy._build_request(options)
+
+ # ensure that the machinery is warmed up before tracing starts.
+ build_request(options)
+ gc.collect()
+
+ tracemalloc.start(1000)
+
+ snapshot_before = tracemalloc.take_snapshot()
+
+ ITERATIONS = 10
+ for _ in range(ITERATIONS):
+ build_request(options)
+
+ gc.collect()
+ snapshot_after = tracemalloc.take_snapshot()
+
+ tracemalloc.stop()
+
+ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.StatisticDiff) -> None:
+ if diff.count == 0:
+ # Avoid false positives by considering only leaks (i.e. allocations that persist).
+ return
+
+ if diff.count % ITERATIONS != 0:
+ # Avoid false positives by considering only leaks that appear per iteration.
+ return
+
+ for frame in diff.traceback:
+ if any(
+ frame.filename.endswith(fragment)
+ for fragment in [
+ # to_raw_response_wrapper leaks through the @functools.wraps() decorator.
+ #
+ # removing the decorator fixes the leak for reasons we don't understand.
+ "opencode/_legacy_response.py",
+ "opencode/_response.py",
+ # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason.
+ "opencode/_compat.py",
+ # Standard library leaks we don't care about.
+ "/logging/__init__.py",
+ ]
+ ):
+ return
+
+ leaks.append(diff)
+
+ leaks: list[tracemalloc.StatisticDiff] = []
+ for diff in snapshot_after.compare_to(snapshot_before, "traceback"):
+ add_leak(leaks, diff)
+ if leaks:
+ for leak in leaks:
+ print("MEMORY LEAK:", leak)
+ for frame in leak.traceback:
+ print(frame)
+ raise AssertionError()
+
+ def test_request_timeout(self, client: Opencode) -> None:
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
+ assert timeout == DEFAULT_TIMEOUT
+
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0)))
+ timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
+ assert timeout == httpx.Timeout(100.0)
+
+ def test_client_timeout_option(self) -> None:
+ client = Opencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ timeout=httpx.Timeout(0),
+ )
+
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
+ assert timeout == httpx.Timeout(0)
+
+ client.close()
+
+ def test_http_client_timeout_option(self) -> None:
+ # custom timeout given to the httpx client should be used
+ with httpx.Client(timeout=None) as http_client:
+ client = Opencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ http_client=http_client,
+ )
+
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
+ assert timeout == httpx.Timeout(None)
+
+ client.close()
+
+ # no timeout given to the httpx client should not use the httpx default
+ with httpx.Client() as http_client:
+ client = Opencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ http_client=http_client,
+ )
+
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
+ assert timeout == DEFAULT_TIMEOUT
+
+ client.close()
+
+ # explicitly passing the default timeout currently results in it being ignored
+ with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
+ client = Opencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ http_client=http_client,
+ )
+
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
+ assert timeout == DEFAULT_TIMEOUT # our default
+
+ client.close()
+
+ async def test_invalid_http_client(self) -> None:
+ with pytest.raises(TypeError, match="Invalid `http_client` arg"):
+ async with httpx.AsyncClient() as http_client:
+ Opencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ http_client=cast(Any, http_client),
+ )
+
+ def test_default_headers_option(self) -> None:
+ test_client = Opencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ default_headers={"X-Foo": "bar"},
+ )
+ request = test_client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ assert request.headers.get("x-foo") == "bar"
+ assert request.headers.get("x-stainless-lang") == "python"
+
+ test_client2 = Opencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ default_headers={
+ "X-Foo": "stainless",
+ "X-Stainless-Lang": "my-overriding-header",
+ },
+ )
+ request = test_client2._build_request(FinalRequestOptions(method="get", url="/foo"))
+ assert request.headers.get("x-foo") == "stainless"
+ assert request.headers.get("x-stainless-lang") == "my-overriding-header"
+
+ test_client.close()
+ test_client2.close()
+
+ def test_validate_headers(self) -> None:
+ client = Opencode(base_url=base_url, username=username, password=password, _strict_response_validation=True)
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ assert "Basic" in request.headers.get("Authorization")
+
+ with pytest.raises(OpencodeError):
+ with update_env(**{"OPENCODE_PASSWORD": Omit()}):
+ client2 = Opencode(base_url=base_url, username=None, password=None, _strict_response_validation=True)
+ _ = client2
+
+ def test_default_query_option(self) -> None:
+ client = Opencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ default_query={"query_param": "bar"},
+ )
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ url = httpx.URL(request.url)
+ assert dict(url.params) == {"query_param": "bar"}
+
+ request = client._build_request(
+ FinalRequestOptions(
+ method="get",
+ url="/foo",
+ params={"foo": "baz", "query_param": "overridden"},
+ )
+ )
+ url = httpx.URL(request.url)
+ assert dict(url.params) == {"foo": "baz", "query_param": "overridden"}
+
+ client.close()
+
+ def test_hardcoded_query_params_in_url(self, client: Opencode) -> None:
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo?beta=true"))
+ url = httpx.URL(request.url)
+ assert dict(url.params) == {"beta": "true"}
+
+ request = client._build_request(
+ FinalRequestOptions(
+ method="get",
+ url="/foo?beta=true",
+ params={"limit": "10", "page": "abc"},
+ )
+ )
+ url = httpx.URL(request.url)
+ assert dict(url.params) == {"beta": "true", "limit": "10", "page": "abc"}
+
+ request = client._build_request(
+ FinalRequestOptions(
+ method="get",
+ url="/files/a%2Fb?beta=true",
+ params={"limit": "10"},
+ )
+ )
+ assert request.url.raw_path == b"/files/a%2Fb?beta=true&limit=10"
+
+ def test_request_extra_json(self, client: Opencode) -> None:
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ json_data={"foo": "bar"},
+ extra_json={"baz": False},
+ ),
+ )
+ data = json.loads(request.content.decode("utf-8"))
+ assert data == {"foo": "bar", "baz": False}
+
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ extra_json={"baz": False},
+ ),
+ )
+ data = json.loads(request.content.decode("utf-8"))
+ assert data == {"baz": False}
+
+ # `extra_json` takes priority over `json_data` when keys clash
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ json_data={"foo": "bar", "baz": True},
+ extra_json={"baz": None},
+ ),
+ )
+ data = json.loads(request.content.decode("utf-8"))
+ assert data == {"foo": "bar", "baz": None}
+
+ def test_request_extra_headers(self, client: Opencode) -> None:
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ **make_request_options(extra_headers={"X-Foo": "Foo"}),
+ ),
+ )
+ assert request.headers.get("X-Foo") == "Foo"
+
+ # `extra_headers` takes priority over `default_headers` when keys clash
+ request = client.with_options(default_headers={"X-Bar": "true"})._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ **make_request_options(
+ extra_headers={"X-Bar": "false"},
+ ),
+ ),
+ )
+ assert request.headers.get("X-Bar") == "false"
+
+ def test_request_extra_query(self, client: Opencode) -> None:
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ **make_request_options(
+ extra_query={"my_query_param": "Foo"},
+ ),
+ ),
+ )
+ params = dict(request.url.params)
+ assert params == {"my_query_param": "Foo"}
+
+ # if both `query` and `extra_query` are given, they are merged
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ **make_request_options(
+ query={"bar": "1"},
+ extra_query={"foo": "2"},
+ ),
+ ),
+ )
+ params = dict(request.url.params)
+ assert params == {"bar": "1", "foo": "2"}
+
+ # `extra_query` takes priority over `query` when keys clash
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ **make_request_options(
+ query={"foo": "1"},
+ extra_query={"foo": "2"},
+ ),
+ ),
+ )
+ params = dict(request.url.params)
+ assert params == {"foo": "2"}
+
+ def test_multipart_repeating_array(self, client: Opencode) -> None:
+ request = client._build_request(
+ FinalRequestOptions.construct(
+ method="post",
+ url="/foo",
+ headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"},
+ json_data={"array": ["foo", "bar"]},
+ files=[("foo.txt", b"hello world")],
+ )
+ )
+
+ assert request.read().split(b"\r\n") == [
+ b"--6b7ba517decee4a450543ea6ae821c82",
+ b'Content-Disposition: form-data; name="array[]"',
+ b"",
+ b"foo",
+ b"--6b7ba517decee4a450543ea6ae821c82",
+ b'Content-Disposition: form-data; name="array[]"',
+ b"",
+ b"bar",
+ b"--6b7ba517decee4a450543ea6ae821c82",
+ b'Content-Disposition: form-data; name="foo.txt"; filename="upload"',
+ b"Content-Type: application/octet-stream",
+ b"",
+ b"hello world",
+ b"--6b7ba517decee4a450543ea6ae821c82--",
+ b"",
+ ]
+
+ @pytest.mark.respx(base_url=base_url)
+ def test_binary_content_upload(self, respx_mock: MockRouter, client: Opencode) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ response = client.post(
+ "/upload",
+ content=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
+ def test_binary_content_upload_with_iterator(self) -> None:
+ file_content = b"Hello, this is a test file."
+ counter = Counter()
+ iterator = _make_sync_iterator([file_content], counter=counter)
+
+ def mock_handler(request: httpx.Request) -> httpx.Response:
+ assert counter.value == 0, "the request body should not have been read"
+ return httpx.Response(200, content=request.read())
+
+ with Opencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ http_client=httpx.Client(transport=MockTransport(handler=mock_handler)),
+ ) as client:
+ response = client.post(
+ "/upload",
+ content=iterator,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+ assert counter.value == 1
+
+ @pytest.mark.respx(base_url=base_url)
+ def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRouter, client: Opencode) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ with pytest.deprecated_call(
+ match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead."
+ ):
+ response = client.post(
+ "/upload",
+ body=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
+ @pytest.mark.respx(base_url=base_url)
+ def test_basic_union_response(self, respx_mock: MockRouter, client: Opencode) -> None:
+ class Model1(BaseModel):
+ name: str
+
+ class Model2(BaseModel):
+ foo: str
+
+ respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+ response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+ assert isinstance(response, Model2)
+ assert response.foo == "bar"
+
+ @pytest.mark.respx(base_url=base_url)
+ def test_union_response_different_types(self, respx_mock: MockRouter, client: Opencode) -> None:
+ """Union of objects with the same field name using a different type"""
+
+ class Model1(BaseModel):
+ foo: int
+
+ class Model2(BaseModel):
+ foo: str
+
+ respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+ response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+ assert isinstance(response, Model2)
+ assert response.foo == "bar"
+
+ respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1}))
+
+ response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+ assert isinstance(response, Model1)
+ assert response.foo == 1
+
+ @pytest.mark.respx(base_url=base_url)
+ def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter, client: Opencode) -> None:
+ """
+ Response that sets Content-Type to something other than application/json but returns json data
+ """
+
+ class Model(BaseModel):
+ foo: int
+
+ respx_mock.get("/foo").mock(
+ return_value=httpx.Response(
+ 200,
+ content=json.dumps({"foo": 2}),
+ headers={"Content-Type": "application/text"},
+ )
+ )
+
+ response = client.get("/foo", cast_to=Model)
+ assert isinstance(response, Model)
+ assert response.foo == 2
+
+ def test_base_url_setter(self) -> None:
+ client = Opencode(
+ base_url="https://example.com/from_init",
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ )
+ assert client.base_url == "https://example.com/from_init/"
+
+ client.base_url = "https://example.com/from_setter" # type: ignore[assignment]
+
+ assert client.base_url == "https://example.com/from_setter/"
+
+ client.close()
+
+ def test_base_url_env(self) -> None:
+ with update_env(OPENCODE_BASE_URL="http://localhost:5000/from/env"):
+ client = Opencode(username=username, password=password, _strict_response_validation=True)
+ assert client.base_url == "http://localhost:5000/from/env/"
+
+ @pytest.mark.parametrize(
+ "client",
+ [
+ Opencode(
+ base_url="http://localhost:5000/custom/path/",
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ ),
+ Opencode(
+ base_url="http://localhost:5000/custom/path/",
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ http_client=httpx.Client(),
+ ),
+ ],
+ ids=["standard", "custom http client"],
)
- result = client.session.prompt("ses_1", text="hi")
- assert result.id == "msg_1"
+ def test_base_url_trailing_slash(self, client: Opencode) -> None:
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ json_data={"foo": "bar"},
+ ),
+ )
+ assert request.url == "http://localhost:5000/custom/path/foo"
+ client.close()
+ @pytest.mark.parametrize(
+ "client",
+ [
+ Opencode(
+ base_url="http://localhost:5000/custom/path/",
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ ),
+ Opencode(
+ base_url="http://localhost:5000/custom/path/",
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ http_client=httpx.Client(),
+ ),
+ ],
+ ids=["standard", "custom http client"],
+ )
+ def test_base_url_no_trailing_slash(self, client: Opencode) -> None:
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ json_data={"foo": "bar"},
+ ),
+ )
+ assert request.url == "http://localhost:5000/custom/path/foo"
+ client.close()
-def test_tagged_error_maps_to_specific_class(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/session/ses_9").respond(
- 404, json={"_tag": "SessionNotFoundError", "sessionID": "ses_9", "message": "Session not found: ses_9"}
+ @pytest.mark.parametrize(
+ "client",
+ [
+ Opencode(
+ base_url="http://localhost:5000/custom/path/",
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ ),
+ Opencode(
+ base_url="http://localhost:5000/custom/path/",
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ http_client=httpx.Client(),
+ ),
+ ],
+ ids=["standard", "custom http client"],
)
- with pytest.raises(SessionNotFoundError) as caught:
- client.session.get("ses_9")
- error = caught.value
- assert isinstance(error, NotFoundError)
- assert error.status_code == 404
- assert error.error.session_id == "ses_9"
- assert "Session not found" in str(error)
+ def test_absolute_request_url(self, client: Opencode) -> None:
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="https://myapi.com/foo",
+ json_data={"foo": "bar"},
+ ),
+ )
+ assert request.url == "https://myapi.com/foo"
+ client.close()
+ def test_copied_client_does_not_close_http(self) -> None:
+ test_client = Opencode(
+ base_url=base_url, username=username, password=password, _strict_response_validation=True
+ )
+ assert not test_client.is_closed()
-def test_unknown_tag_falls_back_to_status_class(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/session/ses_9").respond(404, json={"_tag": "BrandNewError", "message": "nope"})
- with pytest.raises(NotFoundError) as caught:
- client.session.get("ses_9")
- assert caught.value.error_tag == "BrandNewError"
+ copied = test_client.copy()
+ assert copied is not test_client
+ del copied
-def test_get_retries_transient_status(client: Opencode, mock_api: respx.MockRouter) -> None:
- route = mock_api.get("/api/agent").mock(
- side_effect=[httpx.Response(502), httpx.Response(503), httpx.Response(200, json=AGENTS)]
- )
- client.agent.list()
- assert route.call_count == 3
+ assert not test_client.is_closed()
+
+ def test_client_context_manager(self) -> None:
+ test_client = Opencode(
+ base_url=base_url, username=username, password=password, _strict_response_validation=True
+ )
+ with test_client as c2:
+ assert c2 is test_client
+ assert not c2.is_closed()
+ assert not test_client.is_closed()
+ assert test_client.is_closed()
+
+ @pytest.mark.respx(base_url=base_url)
+ def test_client_response_validation_error(self, respx_mock: MockRouter, client: Opencode) -> None:
+ class Model(BaseModel):
+ foo: str
+
+ respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}}))
+
+ with pytest.raises(APIResponseValidationError) as exc:
+ client.get("/foo", cast_to=Model)
+ assert isinstance(exc.value.__cause__, ValidationError)
-def test_retries_are_bounded(client: Opencode, mock_api: respx.MockRouter) -> None:
- route = mock_api.get("/api/agent").respond(500, json={"_tag": "UnknownError", "message": "boom"})
- with pytest.raises(InternalServerError):
- client.agent.list()
- assert route.call_count == 3
+ def test_client_max_retries_validation(self) -> None:
+ with pytest.raises(TypeError, match=r"max_retries cannot be None"):
+ Opencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ max_retries=cast(Any, None),
+ )
+ @pytest.mark.respx(base_url=base_url)
+ def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
+ class Model(BaseModel):
+ name: str
-def test_post_is_not_retried_after_server_error(client: Opencode, mock_api: respx.MockRouter) -> None:
- route = mock_api.post("/api/session/ses_1/prompt").respond(500, json={"message": "boom"})
- with pytest.raises(InternalServerError):
- client.session.prompt("ses_1", text="hi")
- assert route.call_count == 1
+ respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
+ strict_client = Opencode(
+ base_url=base_url, username=username, password=password, _strict_response_validation=True
+ )
-def test_post_is_retried_after_rate_limit(client: Opencode, mock_api: respx.MockRouter) -> None:
- route = mock_api.post("/api/session").mock(
- side_effect=[httpx.Response(429, headers={"retry-after": "0"}), httpx.Response(200, json={"data": SESSION})]
+ with pytest.raises(APIResponseValidationError):
+ strict_client.get("/foo", cast_to=Model)
+
+ non_strict_client = Opencode(
+ base_url=base_url, username=username, password=password, _strict_response_validation=False
+ )
+
+ response = non_strict_client.get("/foo", cast_to=Model)
+ assert isinstance(response, str) # type: ignore[unreachable]
+
+ strict_client.close()
+ non_strict_client.close()
+
+ @pytest.mark.parametrize(
+ "remaining_retries,retry_after,timeout",
+ [
+ [3, "20", 20],
+ [3, "0", 0.5],
+ [3, "-10", 0.5],
+ [3, "60", 60],
+ [3, "61", 0.5],
+ [3, "Fri, 29 Sep 2023 16:26:57 GMT", 20],
+ [3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.5],
+ [3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.5],
+ [3, "Fri, 29 Sep 2023 16:27:37 GMT", 60],
+ [3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.5],
+ [3, "99999999999999999999999999999999999", 0.5],
+ [3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.5],
+ [3, "", 0.5],
+ [2, "", 0.5 * 2.0],
+ [1, "", 0.5 * 4.0],
+ [-1100, "", 8], # test large number potentially overflowing
+ ],
)
- client.session.create(title="t")
- assert route.call_count == 2
+ @mock.patch("time.time", mock.MagicMock(return_value=1696004797))
+ def test_parse_retry_after_header(
+ self, remaining_retries: int, retry_after: str, timeout: float, client: Opencode
+ ) -> None:
+ headers = httpx.Headers({"retry-after": retry_after})
+ options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
+ calculated = client._calculate_retry_timeout(remaining_retries, options, headers)
+ assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType]
+
+ @mock.patch("opencode._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+ @pytest.mark.respx(base_url=base_url)
+ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: Opencode) -> None:
+ respx_mock.get("/api/agent").mock(side_effect=httpx.TimeoutException("Test timeout error"))
+
+ with pytest.raises(APITimeoutError):
+ client.agent.with_streaming_response.list().__enter__()
+
+ assert _get_open_connections(client) == 0
+
+ @mock.patch("opencode._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+ @pytest.mark.respx(base_url=base_url)
+ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: Opencode) -> None:
+ respx_mock.get("/api/agent").mock(return_value=httpx.Response(500))
+
+ with pytest.raises(APIStatusError):
+ client.agent.with_streaming_response.list().__enter__()
+ assert _get_open_connections(client) == 0
+
+ @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
+ @mock.patch("opencode._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+ @pytest.mark.respx(base_url=base_url)
+ @pytest.mark.parametrize("failure_mode", ["status", "exception"])
+ def test_retries_taken(
+ self,
+ client: Opencode,
+ failures_before_success: int,
+ failure_mode: Literal["status", "exception"],
+ respx_mock: MockRouter,
+ ) -> None:
+ client = client.with_options(max_retries=4)
+
+ nb_retries = 0
+
+ def retry_handler(_request: httpx.Request) -> httpx.Response:
+ nonlocal nb_retries
+ if nb_retries < failures_before_success:
+ nb_retries += 1
+ if failure_mode == "exception":
+ raise RuntimeError("oops")
+ return httpx.Response(500)
+ return httpx.Response(200)
+
+ respx_mock.get("/api/agent").mock(side_effect=retry_handler)
+
+ response = client.agent.with_raw_response.list()
+
+ assert response.retries_taken == failures_before_success
+ assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success
+
+ @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
+ @mock.patch("opencode._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+ @pytest.mark.respx(base_url=base_url)
+ def test_omit_retry_count_header(
+ self, client: Opencode, failures_before_success: int, respx_mock: MockRouter
+ ) -> None:
+ client = client.with_options(max_retries=4)
+
+ nb_retries = 0
+
+ def retry_handler(_request: httpx.Request) -> httpx.Response:
+ nonlocal nb_retries
+ if nb_retries < failures_before_success:
+ nb_retries += 1
+ return httpx.Response(500)
+ return httpx.Response(200)
+
+ respx_mock.get("/api/agent").mock(side_effect=retry_handler)
+
+ response = client.agent.with_raw_response.list(extra_headers={"x-stainless-retry-count": Omit()})
+
+ assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0
+
+ @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
+ @mock.patch("opencode._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+ @pytest.mark.respx(base_url=base_url)
+ def test_overwrite_retry_count_header(
+ self, client: Opencode, failures_before_success: int, respx_mock: MockRouter
+ ) -> None:
+ client = client.with_options(max_retries=4)
+
+ nb_retries = 0
+
+ def retry_handler(_request: httpx.Request) -> httpx.Response:
+ nonlocal nb_retries
+ if nb_retries < failures_before_success:
+ nb_retries += 1
+ return httpx.Response(500)
+ return httpx.Response(200)
+
+ respx_mock.get("/api/agent").mock(side_effect=retry_handler)
+
+ response = client.agent.with_raw_response.list(extra_headers={"x-stainless-retry-count": "42"})
+
+ assert response.http_request.headers.get("x-stainless-retry-count") == "42"
+
+ def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
+ # Test that the proxy environment variables are set correctly
+ monkeypatch.setenv("HTTPS_PROXY", "https://example.org")
+ # Delete in case our environment has any proxy env vars set
+ monkeypatch.delenv("HTTP_PROXY", raising=False)
+ monkeypatch.delenv("ALL_PROXY", raising=False)
+ monkeypatch.delenv("NO_PROXY", raising=False)
+ monkeypatch.delenv("http_proxy", raising=False)
+ monkeypatch.delenv("https_proxy", raising=False)
+ monkeypatch.delenv("all_proxy", raising=False)
+ monkeypatch.delenv("no_proxy", raising=False)
+
+ client = DefaultHttpxClient()
+
+ mounts = tuple(client._mounts.items())
+ assert len(mounts) == 1
+ assert mounts[0][0].pattern == "https://"
+
+ @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning")
+ def test_default_client_creation(self) -> None:
+ # Ensure that the client can be initialized without any exceptions
+ DefaultHttpxClient(
+ verify=True,
+ cert=None,
+ trust_env=True,
+ http1=True,
+ http2=False,
+ limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
+ )
+
+ @pytest.mark.respx(base_url=base_url)
+ def test_follow_redirects(self, respx_mock: MockRouter, client: Opencode) -> None:
+ # Test that the default follow_redirects=True allows following redirects
+ respx_mock.post("/redirect").mock(
+ return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
+ )
+ respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"}))
+
+ response = client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
+ assert response.status_code == 200
+ assert response.json() == {"status": "ok"}
+ @pytest.mark.respx(base_url=base_url)
+ def test_follow_redirects_disabled(self, respx_mock: MockRouter, client: Opencode) -> None:
+ # Test that follow_redirects=False prevents following redirects
+ respx_mock.post("/redirect").mock(
+ return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
+ )
-def test_post_rate_limit_raises_after_retries(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.post("/api/session").respond(429, json={"message": "slow down"})
- with pytest.raises(RateLimitError):
- client.session.create(title="t")
+ with pytest.raises(APIStatusError) as exc_info:
+ client.post("/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response)
+ assert exc_info.value.response.status_code == 302
+ assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected"
-def test_connect_errors_are_retried_even_for_post(client: Opencode, mock_api: respx.MockRouter) -> None:
- route = mock_api.post("/api/session").mock(
- side_effect=[httpx.ConnectError("refused"), httpx.Response(200, json={"data": SESSION})]
+
+class TestAsyncOpencode:
+ @pytest.mark.respx(base_url=base_url)
+ async def test_raw_response(self, respx_mock: MockRouter, async_client: AsyncOpencode) -> None:
+ respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+ response = await async_client.post("/foo", cast_to=httpx.Response)
+ assert response.status_code == 200
+ assert isinstance(response, httpx.Response)
+ assert response.json() == {"foo": "bar"}
+
+ @pytest.mark.respx(base_url=base_url)
+ async def test_raw_response_for_binary(self, respx_mock: MockRouter, async_client: AsyncOpencode) -> None:
+ respx_mock.post("/foo").mock(
+ return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}')
+ )
+
+ response = await async_client.post("/foo", cast_to=httpx.Response)
+ assert response.status_code == 200
+ assert isinstance(response, httpx.Response)
+ assert response.json() == {"foo": "bar"}
+
+ def test_copy(self, async_client: AsyncOpencode) -> None:
+ copied = async_client.copy()
+ assert id(copied) != id(async_client)
+
+ copied = async_client.copy(username="another My Username")
+ assert copied.username == "another My Username"
+ assert async_client.username == "My Username"
+
+ copied = async_client.copy(password="another My Password")
+ assert copied.password == "another My Password"
+ assert async_client.password == "My Password"
+
+ def test_copy_default_options(self, async_client: AsyncOpencode) -> None:
+ # options that have a default are overridden correctly
+ copied = async_client.copy(max_retries=7)
+ assert copied.max_retries == 7
+ assert async_client.max_retries == 2
+
+ copied2 = copied.copy(max_retries=6)
+ assert copied2.max_retries == 6
+ assert copied.max_retries == 7
+
+ # timeout
+ assert isinstance(async_client.timeout, httpx.Timeout)
+ copied = async_client.copy(timeout=None)
+ assert copied.timeout is None
+ assert isinstance(async_client.timeout, httpx.Timeout)
+
+ async def test_copy_default_headers(self) -> None:
+ client = AsyncOpencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ default_headers={"X-Foo": "bar"},
+ )
+ assert client.default_headers["X-Foo"] == "bar"
+
+ # does not override the already given value when not specified
+ copied = client.copy()
+ assert copied.default_headers["X-Foo"] == "bar"
+
+ # merges already given headers
+ copied = client.copy(default_headers={"X-Bar": "stainless"})
+ assert copied.default_headers["X-Foo"] == "bar"
+ assert copied.default_headers["X-Bar"] == "stainless"
+
+ # uses new values for any already given headers
+ copied = client.copy(default_headers={"X-Foo": "stainless"})
+ assert copied.default_headers["X-Foo"] == "stainless"
+
+ # set_default_headers
+
+ # completely overrides already set values
+ copied = client.copy(set_default_headers={})
+ assert copied.default_headers.get("X-Foo") is None
+
+ copied = client.copy(set_default_headers={"X-Bar": "Robert"})
+ assert copied.default_headers["X-Bar"] == "Robert"
+
+ with pytest.raises(
+ ValueError,
+ match="`default_headers` and `set_default_headers` arguments are mutually exclusive",
+ ):
+ client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"})
+ await client.close()
+
+ async def test_copy_default_query(self) -> None:
+ client = AsyncOpencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ default_query={"foo": "bar"},
+ )
+ assert _get_params(client)["foo"] == "bar"
+
+ # does not override the already given value when not specified
+ copied = client.copy()
+ assert _get_params(copied)["foo"] == "bar"
+
+ # merges already given params
+ copied = client.copy(default_query={"bar": "stainless"})
+ params = _get_params(copied)
+ assert params["foo"] == "bar"
+ assert params["bar"] == "stainless"
+
+ # uses new values for any already given headers
+ copied = client.copy(default_query={"foo": "stainless"})
+ assert _get_params(copied)["foo"] == "stainless"
+
+ # set_default_query
+
+ # completely overrides already set values
+ copied = client.copy(set_default_query={})
+ assert _get_params(copied) == {}
+
+ copied = client.copy(set_default_query={"bar": "Robert"})
+ assert _get_params(copied)["bar"] == "Robert"
+
+ with pytest.raises(
+ ValueError,
+ # TODO: update
+ match="`default_query` and `set_default_query` arguments are mutually exclusive",
+ ):
+ client.copy(set_default_query={}, default_query={"foo": "Bar"})
+
+ await client.close()
+
+ def test_copy_signature(self, async_client: AsyncOpencode) -> None:
+ # ensure the same parameters that can be passed to the client are defined in the `.copy()` method
+ init_signature = inspect.signature(
+ # mypy doesn't like that we access the `__init__` property.
+ async_client.__init__, # type: ignore[misc]
+ )
+ copy_signature = inspect.signature(async_client.copy)
+ exclude_params = {"transport", "proxies", "_strict_response_validation"}
+
+ for name in init_signature.parameters.keys():
+ if name in exclude_params:
+ continue
+
+ copy_param = copy_signature.parameters.get(name)
+ assert copy_param is not None, f"copy() signature is missing the {name} param"
+
+ @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12")
+ def test_copy_build_request(self, async_client: AsyncOpencode) -> None:
+ options = FinalRequestOptions(method="get", url="/foo")
+
+ def build_request(options: FinalRequestOptions) -> None:
+ client_copy = async_client.copy()
+ client_copy._build_request(options)
+
+ # ensure that the machinery is warmed up before tracing starts.
+ build_request(options)
+ gc.collect()
+
+ tracemalloc.start(1000)
+
+ snapshot_before = tracemalloc.take_snapshot()
+
+ ITERATIONS = 10
+ for _ in range(ITERATIONS):
+ build_request(options)
+
+ gc.collect()
+ snapshot_after = tracemalloc.take_snapshot()
+
+ tracemalloc.stop()
+
+ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.StatisticDiff) -> None:
+ if diff.count == 0:
+ # Avoid false positives by considering only leaks (i.e. allocations that persist).
+ return
+
+ if diff.count % ITERATIONS != 0:
+ # Avoid false positives by considering only leaks that appear per iteration.
+ return
+
+ for frame in diff.traceback:
+ if any(
+ frame.filename.endswith(fragment)
+ for fragment in [
+ # to_raw_response_wrapper leaks through the @functools.wraps() decorator.
+ #
+ # removing the decorator fixes the leak for reasons we don't understand.
+ "opencode/_legacy_response.py",
+ "opencode/_response.py",
+ # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason.
+ "opencode/_compat.py",
+ # Standard library leaks we don't care about.
+ "/logging/__init__.py",
+ ]
+ ):
+ return
+
+ leaks.append(diff)
+
+ leaks: list[tracemalloc.StatisticDiff] = []
+ for diff in snapshot_after.compare_to(snapshot_before, "traceback"):
+ add_leak(leaks, diff)
+ if leaks:
+ for leak in leaks:
+ print("MEMORY LEAK:", leak)
+ for frame in leak.traceback:
+ print(frame)
+ raise AssertionError()
+
+ async def test_request_timeout(self, async_client: AsyncOpencode) -> None:
+ request = async_client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
+ assert timeout == DEFAULT_TIMEOUT
+
+ request = async_client._build_request(
+ FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0))
+ )
+ timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
+ assert timeout == httpx.Timeout(100.0)
+
+ async def test_client_timeout_option(self) -> None:
+ client = AsyncOpencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ timeout=httpx.Timeout(0),
+ )
+
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
+ assert timeout == httpx.Timeout(0)
+
+ await client.close()
+
+ async def test_http_client_timeout_option(self) -> None:
+ # custom timeout given to the httpx client should be used
+ async with httpx.AsyncClient(timeout=None) as http_client:
+ client = AsyncOpencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ http_client=http_client,
+ )
+
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
+ assert timeout == httpx.Timeout(None)
+
+ await client.close()
+
+ # no timeout given to the httpx client should not use the httpx default
+ async with httpx.AsyncClient() as http_client:
+ client = AsyncOpencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ http_client=http_client,
+ )
+
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
+ assert timeout == DEFAULT_TIMEOUT
+
+ await client.close()
+
+ # explicitly passing the default timeout currently results in it being ignored
+ async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
+ client = AsyncOpencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ http_client=http_client,
+ )
+
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
+ assert timeout == DEFAULT_TIMEOUT # our default
+
+ await client.close()
+
+ def test_invalid_http_client(self) -> None:
+ with pytest.raises(TypeError, match="Invalid `http_client` arg"):
+ with httpx.Client() as http_client:
+ AsyncOpencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ http_client=cast(Any, http_client),
+ )
+
+ async def test_default_headers_option(self) -> None:
+ test_client = AsyncOpencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ default_headers={"X-Foo": "bar"},
+ )
+ request = test_client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ assert request.headers.get("x-foo") == "bar"
+ assert request.headers.get("x-stainless-lang") == "python"
+
+ test_client2 = AsyncOpencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ default_headers={
+ "X-Foo": "stainless",
+ "X-Stainless-Lang": "my-overriding-header",
+ },
+ )
+ request = test_client2._build_request(FinalRequestOptions(method="get", url="/foo"))
+ assert request.headers.get("x-foo") == "stainless"
+ assert request.headers.get("x-stainless-lang") == "my-overriding-header"
+
+ await test_client.close()
+ await test_client2.close()
+
+ def test_validate_headers(self) -> None:
+ client = AsyncOpencode(
+ base_url=base_url, username=username, password=password, _strict_response_validation=True
+ )
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ assert "Basic" in request.headers.get("Authorization")
+
+ with pytest.raises(OpencodeError):
+ with update_env(**{"OPENCODE_PASSWORD": Omit()}):
+ client2 = AsyncOpencode(
+ base_url=base_url, username=None, password=None, _strict_response_validation=True
+ )
+ _ = client2
+
+ async def test_default_query_option(self) -> None:
+ client = AsyncOpencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ default_query={"query_param": "bar"},
+ )
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ url = httpx.URL(request.url)
+ assert dict(url.params) == {"query_param": "bar"}
+
+ request = client._build_request(
+ FinalRequestOptions(
+ method="get",
+ url="/foo",
+ params={"foo": "baz", "query_param": "overridden"},
+ )
+ )
+ url = httpx.URL(request.url)
+ assert dict(url.params) == {"foo": "baz", "query_param": "overridden"}
+
+ await client.close()
+
+ async def test_hardcoded_query_params_in_url(self, async_client: AsyncOpencode) -> None:
+ request = async_client._build_request(FinalRequestOptions(method="get", url="/foo?beta=true"))
+ url = httpx.URL(request.url)
+ assert dict(url.params) == {"beta": "true"}
+
+ request = async_client._build_request(
+ FinalRequestOptions(
+ method="get",
+ url="/foo?beta=true",
+ params={"limit": "10", "page": "abc"},
+ )
+ )
+ url = httpx.URL(request.url)
+ assert dict(url.params) == {"beta": "true", "limit": "10", "page": "abc"}
+
+ request = async_client._build_request(
+ FinalRequestOptions(
+ method="get",
+ url="/files/a%2Fb?beta=true",
+ params={"limit": "10"},
+ )
+ )
+ assert request.url.raw_path == b"/files/a%2Fb?beta=true&limit=10"
+
+ def test_request_extra_json(self, client: Opencode) -> None:
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ json_data={"foo": "bar"},
+ extra_json={"baz": False},
+ ),
+ )
+ data = json.loads(request.content.decode("utf-8"))
+ assert data == {"foo": "bar", "baz": False}
+
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ extra_json={"baz": False},
+ ),
+ )
+ data = json.loads(request.content.decode("utf-8"))
+ assert data == {"baz": False}
+
+ # `extra_json` takes priority over `json_data` when keys clash
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ json_data={"foo": "bar", "baz": True},
+ extra_json={"baz": None},
+ ),
+ )
+ data = json.loads(request.content.decode("utf-8"))
+ assert data == {"foo": "bar", "baz": None}
+
+ def test_request_extra_headers(self, client: Opencode) -> None:
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ **make_request_options(extra_headers={"X-Foo": "Foo"}),
+ ),
+ )
+ assert request.headers.get("X-Foo") == "Foo"
+
+ # `extra_headers` takes priority over `default_headers` when keys clash
+ request = client.with_options(default_headers={"X-Bar": "true"})._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ **make_request_options(
+ extra_headers={"X-Bar": "false"},
+ ),
+ ),
+ )
+ assert request.headers.get("X-Bar") == "false"
+
+ def test_request_extra_query(self, client: Opencode) -> None:
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ **make_request_options(
+ extra_query={"my_query_param": "Foo"},
+ ),
+ ),
+ )
+ params = dict(request.url.params)
+ assert params == {"my_query_param": "Foo"}
+
+ # if both `query` and `extra_query` are given, they are merged
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ **make_request_options(
+ query={"bar": "1"},
+ extra_query={"foo": "2"},
+ ),
+ ),
+ )
+ params = dict(request.url.params)
+ assert params == {"bar": "1", "foo": "2"}
+
+ # `extra_query` takes priority over `query` when keys clash
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ **make_request_options(
+ query={"foo": "1"},
+ extra_query={"foo": "2"},
+ ),
+ ),
+ )
+ params = dict(request.url.params)
+ assert params == {"foo": "2"}
+
+ def test_multipart_repeating_array(self, async_client: AsyncOpencode) -> None:
+ request = async_client._build_request(
+ FinalRequestOptions.construct(
+ method="post",
+ url="/foo",
+ headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"},
+ json_data={"array": ["foo", "bar"]},
+ files=[("foo.txt", b"hello world")],
+ )
+ )
+
+ assert request.read().split(b"\r\n") == [
+ b"--6b7ba517decee4a450543ea6ae821c82",
+ b'Content-Disposition: form-data; name="array[]"',
+ b"",
+ b"foo",
+ b"--6b7ba517decee4a450543ea6ae821c82",
+ b'Content-Disposition: form-data; name="array[]"',
+ b"",
+ b"bar",
+ b"--6b7ba517decee4a450543ea6ae821c82",
+ b'Content-Disposition: form-data; name="foo.txt"; filename="upload"',
+ b"Content-Type: application/octet-stream",
+ b"",
+ b"hello world",
+ b"--6b7ba517decee4a450543ea6ae821c82--",
+ b"",
+ ]
+
+ @pytest.mark.respx(base_url=base_url)
+ async def test_binary_content_upload(self, respx_mock: MockRouter, async_client: AsyncOpencode) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ response = await async_client.post(
+ "/upload",
+ content=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
+ async def test_binary_content_upload_with_asynciterator(self) -> None:
+ file_content = b"Hello, this is a test file."
+ counter = Counter()
+ iterator = _make_async_iterator([file_content], counter=counter)
+
+ async def mock_handler(request: httpx.Request) -> httpx.Response:
+ assert counter.value == 0, "the request body should not have been read"
+ return httpx.Response(200, content=await request.aread())
+
+ async with AsyncOpencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)),
+ ) as client:
+ response = await client.post(
+ "/upload",
+ content=iterator,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+ assert counter.value == 1
+
+ @pytest.mark.respx(base_url=base_url)
+ async def test_binary_content_upload_with_body_is_deprecated(
+ self, respx_mock: MockRouter, async_client: AsyncOpencode
+ ) -> None:
+ respx_mock.post("/upload").mock(side_effect=mirror_request_content)
+
+ file_content = b"Hello, this is a test file."
+
+ with pytest.deprecated_call(
+ match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead."
+ ):
+ response = await async_client.post(
+ "/upload",
+ body=file_content,
+ cast_to=httpx.Response,
+ options={"headers": {"Content-Type": "application/octet-stream"}},
+ )
+
+ assert response.status_code == 200
+ assert response.request.headers["Content-Type"] == "application/octet-stream"
+ assert response.content == file_content
+
+ @pytest.mark.respx(base_url=base_url)
+ async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncOpencode) -> None:
+ class Model1(BaseModel):
+ name: str
+
+ class Model2(BaseModel):
+ foo: str
+
+ respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+ response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+ assert isinstance(response, Model2)
+ assert response.foo == "bar"
+
+ @pytest.mark.respx(base_url=base_url)
+ async def test_union_response_different_types(self, respx_mock: MockRouter, async_client: AsyncOpencode) -> None:
+ """Union of objects with the same field name using a different type"""
+
+ class Model1(BaseModel):
+ foo: int
+
+ class Model2(BaseModel):
+ foo: str
+
+ respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+ response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+ assert isinstance(response, Model2)
+ assert response.foo == "bar"
+
+ respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1}))
+
+ response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+ assert isinstance(response, Model1)
+ assert response.foo == 1
+
+ @pytest.mark.respx(base_url=base_url)
+ async def test_non_application_json_content_type_for_json_data(
+ self, respx_mock: MockRouter, async_client: AsyncOpencode
+ ) -> None:
+ """
+ Response that sets Content-Type to something other than application/json but returns json data
+ """
+
+ class Model(BaseModel):
+ foo: int
+
+ respx_mock.get("/foo").mock(
+ return_value=httpx.Response(
+ 200,
+ content=json.dumps({"foo": 2}),
+ headers={"Content-Type": "application/text"},
+ )
+ )
+
+ response = await async_client.get("/foo", cast_to=Model)
+ assert isinstance(response, Model)
+ assert response.foo == 2
+
+ async def test_base_url_setter(self) -> None:
+ client = AsyncOpencode(
+ base_url="https://example.com/from_init",
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ )
+ assert client.base_url == "https://example.com/from_init/"
+
+ client.base_url = "https://example.com/from_setter" # type: ignore[assignment]
+
+ assert client.base_url == "https://example.com/from_setter/"
+
+ await client.close()
+
+ async def test_base_url_env(self) -> None:
+ with update_env(OPENCODE_BASE_URL="http://localhost:5000/from/env"):
+ client = AsyncOpencode(username=username, password=password, _strict_response_validation=True)
+ assert client.base_url == "http://localhost:5000/from/env/"
+
+ @pytest.mark.parametrize(
+ "client",
+ [
+ AsyncOpencode(
+ base_url="http://localhost:5000/custom/path/",
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ ),
+ AsyncOpencode(
+ base_url="http://localhost:5000/custom/path/",
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ http_client=httpx.AsyncClient(),
+ ),
+ ],
+ ids=["standard", "custom http client"],
)
- client.session.create(title="t")
- assert route.call_count == 2
+ async def test_base_url_trailing_slash(self, client: AsyncOpencode) -> None:
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ json_data={"foo": "bar"},
+ ),
+ )
+ assert request.url == "http://localhost:5000/custom/path/foo"
+ await client.close()
+ @pytest.mark.parametrize(
+ "client",
+ [
+ AsyncOpencode(
+ base_url="http://localhost:5000/custom/path/",
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ ),
+ AsyncOpencode(
+ base_url="http://localhost:5000/custom/path/",
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ http_client=httpx.AsyncClient(),
+ ),
+ ],
+ ids=["standard", "custom http client"],
+ )
+ async def test_base_url_no_trailing_slash(self, client: AsyncOpencode) -> None:
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="/foo",
+ json_data={"foo": "bar"},
+ ),
+ )
+ assert request.url == "http://localhost:5000/custom/path/foo"
+ await client.close()
-def test_read_timeout_on_post_is_not_retried(client: Opencode, mock_api: respx.MockRouter) -> None:
- route = mock_api.post("/api/session").mock(side_effect=httpx.ReadTimeout("slow"))
- with pytest.raises(APITimeoutError):
- client.session.create(title="t")
- assert route.call_count == 1
+ @pytest.mark.parametrize(
+ "client",
+ [
+ AsyncOpencode(
+ base_url="http://localhost:5000/custom/path/",
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ ),
+ AsyncOpencode(
+ base_url="http://localhost:5000/custom/path/",
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ http_client=httpx.AsyncClient(),
+ ),
+ ],
+ ids=["standard", "custom http client"],
+ )
+ async def test_absolute_request_url(self, client: AsyncOpencode) -> None:
+ request = client._build_request(
+ FinalRequestOptions(
+ method="post",
+ url="https://myapi.com/foo",
+ json_data={"foo": "bar"},
+ ),
+ )
+ assert request.url == "https://myapi.com/foo"
+ await client.close()
+ async def test_copied_client_does_not_close_http(self) -> None:
+ test_client = AsyncOpencode(
+ base_url=base_url, username=username, password=password, _strict_response_validation=True
+ )
+ assert not test_client.is_closed()
-def test_connection_error_after_retries(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/agent").mock(side_effect=httpx.ConnectError("refused"))
- with pytest.raises(APIConnectionError):
- client.agent.list()
+ copied = test_client.copy()
+ assert copied is not test_client
+ del copied
-def test_invalid_response_does_not_echo_values(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/session/ses_1").respond(json={"data": {"id": 12345, "secretField": "token-abc"}})
- with pytest.raises(APIResponseValidationError) as caught:
- client.session.get("ses_1")
- assert "token-abc" not in str(caught.value)
+ await asyncio.sleep(0.2)
+ assert not test_client.is_closed()
+ async def test_client_context_manager(self) -> None:
+ test_client = AsyncOpencode(
+ base_url=base_url, username=username, password=password, _strict_response_validation=True
+ )
+ async with test_client as c2:
+ assert c2 is test_client
+ assert not c2.is_closed()
+ assert not test_client.is_closed()
+ assert test_client.is_closed()
-def test_binary_response(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/fs/read/src/a%20b.py").respond(content=b"print(1)\n")
- assert client.file.read(path="src/a b.py") == b"print(1)\n"
+ @pytest.mark.respx(base_url=base_url)
+ async def test_client_response_validation_error(self, respx_mock: MockRouter, async_client: AsyncOpencode) -> None:
+ class Model(BaseModel):
+ foo: str
+ respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}}))
-def test_void_response(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.delete("/api/session/ses_1").respond(204)
- assert client.session.remove("ses_1") is None
+ with pytest.raises(APIResponseValidationError) as exc:
+ await async_client.get("/foo", cast_to=Model)
+ assert isinstance(exc.value.__cause__, ValidationError)
-def test_with_options_shares_pool_and_overrides(client: Opencode, mock_api: respx.MockRouter) -> None:
- route = mock_api.get("/api/agent").respond(json=AGENTS)
- other = client.with_options(directory="/elsewhere", max_retries=0)
- other.agent.list()
- assert other._http is client._http
- assert route.calls.last.request.url.params["location[directory]"] == "/elsewhere"
- assert route.calls.last.request.headers["Authorization"] == _auth()
+ async def test_client_max_retries_validation(self) -> None:
+ with pytest.raises(TypeError, match=r"max_retries cannot be None"):
+ AsyncOpencode(
+ base_url=base_url,
+ username=username,
+ password=password,
+ _strict_response_validation=True,
+ max_retries=cast(Any, None),
+ )
+ @pytest.mark.respx(base_url=base_url)
+ async def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
+ class Model(BaseModel):
+ name: str
-def test_not_given_is_falsy_singleton() -> None:
- assert not NOT_GIVEN
- assert repr(NOT_GIVEN) == "NOT_GIVEN"
- assert type(NOT_GIVEN)() is NOT_GIVEN
+ respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format"))
+ strict_client = AsyncOpencode(
+ base_url=base_url, username=username, password=password, _strict_response_validation=True
+ )
-def test_message_list_returns_typed_union(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/session/ses_1/message").respond(
- json={
- "data": [{"type": "user", "id": "msg_1", "time": {"created": 1}, "text": "hi"}],
- "cursor": {"previous": None, "next": None},
- }
+ with pytest.raises(APIResponseValidationError):
+ await strict_client.get("/foo", cast_to=Model)
+
+ non_strict_client = AsyncOpencode(
+ base_url=base_url, username=username, password=password, _strict_response_validation=False
+ )
+
+ response = await non_strict_client.get("/foo", cast_to=Model)
+ assert isinstance(response, str) # type: ignore[unreachable]
+
+ await strict_client.close()
+ await non_strict_client.close()
+
+ @pytest.mark.parametrize(
+ "remaining_retries,retry_after,timeout",
+ [
+ [3, "20", 20],
+ [3, "0", 0.5],
+ [3, "-10", 0.5],
+ [3, "60", 60],
+ [3, "61", 0.5],
+ [3, "Fri, 29 Sep 2023 16:26:57 GMT", 20],
+ [3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.5],
+ [3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.5],
+ [3, "Fri, 29 Sep 2023 16:27:37 GMT", 60],
+ [3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.5],
+ [3, "99999999999999999999999999999999999", 0.5],
+ [3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.5],
+ [3, "", 0.5],
+ [2, "", 0.5 * 2.0],
+ [1, "", 0.5 * 4.0],
+ [-1100, "", 8], # test large number potentially overflowing
+ ],
)
- messages = list(client.message.list("ses_1"))
- assert isinstance(messages[0], SessionMessageUser)
+ @mock.patch("time.time", mock.MagicMock(return_value=1696004797))
+ async def test_parse_retry_after_header(
+ self, remaining_retries: int, retry_after: str, timeout: float, async_client: AsyncOpencode
+ ) -> None:
+ headers = httpx.Headers({"retry-after": retry_after})
+ options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
+ calculated = async_client._calculate_retry_timeout(remaining_retries, options, headers)
+ assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType]
+
+ @mock.patch("opencode._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+ @pytest.mark.respx(base_url=base_url)
+ async def test_retrying_timeout_errors_doesnt_leak(
+ self, respx_mock: MockRouter, async_client: AsyncOpencode
+ ) -> None:
+ respx_mock.get("/api/agent").mock(side_effect=httpx.TimeoutException("Test timeout error"))
+
+ with pytest.raises(APITimeoutError):
+ await async_client.agent.with_streaming_response.list().__aenter__()
+
+ assert _get_open_connections(async_client) == 0
+
+ @mock.patch("opencode._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+ @pytest.mark.respx(base_url=base_url)
+ async def test_retrying_status_errors_doesnt_leak(
+ self, respx_mock: MockRouter, async_client: AsyncOpencode
+ ) -> None:
+ respx_mock.get("/api/agent").mock(return_value=httpx.Response(500))
+
+ with pytest.raises(APIStatusError):
+ await async_client.agent.with_streaming_response.list().__aenter__()
+ assert _get_open_connections(async_client) == 0
+
+ @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
+ @mock.patch("opencode._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+ @pytest.mark.respx(base_url=base_url)
+ @pytest.mark.parametrize("failure_mode", ["status", "exception"])
+ async def test_retries_taken(
+ self,
+ async_client: AsyncOpencode,
+ failures_before_success: int,
+ failure_mode: Literal["status", "exception"],
+ respx_mock: MockRouter,
+ ) -> None:
+ client = async_client.with_options(max_retries=4)
+
+ nb_retries = 0
+
+ def retry_handler(_request: httpx.Request) -> httpx.Response:
+ nonlocal nb_retries
+ if nb_retries < failures_before_success:
+ nb_retries += 1
+ if failure_mode == "exception":
+ raise RuntimeError("oops")
+ return httpx.Response(500)
+ return httpx.Response(200)
+
+ respx_mock.get("/api/agent").mock(side_effect=retry_handler)
+
+ response = await client.agent.with_raw_response.list()
+
+ assert response.retries_taken == failures_before_success
+ assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success
+
+ @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
+ @mock.patch("opencode._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+ @pytest.mark.respx(base_url=base_url)
+ async def test_omit_retry_count_header(
+ self, async_client: AsyncOpencode, failures_before_success: int, respx_mock: MockRouter
+ ) -> None:
+ client = async_client.with_options(max_retries=4)
+
+ nb_retries = 0
+
+ def retry_handler(_request: httpx.Request) -> httpx.Response:
+ nonlocal nb_retries
+ if nb_retries < failures_before_success:
+ nb_retries += 1
+ return httpx.Response(500)
+ return httpx.Response(200)
+
+ respx_mock.get("/api/agent").mock(side_effect=retry_handler)
+
+ response = await client.agent.with_raw_response.list(extra_headers={"x-stainless-retry-count": Omit()})
+
+ assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0
+
+ @pytest.mark.parametrize("failures_before_success", [0, 2, 4])
+ @mock.patch("opencode._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
+ @pytest.mark.respx(base_url=base_url)
+ async def test_overwrite_retry_count_header(
+ self, async_client: AsyncOpencode, failures_before_success: int, respx_mock: MockRouter
+ ) -> None:
+ client = async_client.with_options(max_retries=4)
+
+ nb_retries = 0
+
+ def retry_handler(_request: httpx.Request) -> httpx.Response:
+ nonlocal nb_retries
+ if nb_retries < failures_before_success:
+ nb_retries += 1
+ return httpx.Response(500)
+ return httpx.Response(200)
+
+ respx_mock.get("/api/agent").mock(side_effect=retry_handler)
+
+ response = await client.agent.with_raw_response.list(extra_headers={"x-stainless-retry-count": "42"})
+
+ assert response.http_request.headers.get("x-stainless-retry-count") == "42"
+
+ async def test_get_platform(self) -> None:
+ platform = await asyncify(get_platform)()
+ assert isinstance(platform, (str, OtherPlatform))
+
+ async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
+ # Test that the proxy environment variables are set correctly
+ monkeypatch.setenv("HTTPS_PROXY", "https://example.org")
+ # Delete in case our environment has any proxy env vars set
+ monkeypatch.delenv("HTTP_PROXY", raising=False)
+ monkeypatch.delenv("ALL_PROXY", raising=False)
+ monkeypatch.delenv("NO_PROXY", raising=False)
+ monkeypatch.delenv("http_proxy", raising=False)
+ monkeypatch.delenv("https_proxy", raising=False)
+ monkeypatch.delenv("all_proxy", raising=False)
+ monkeypatch.delenv("no_proxy", raising=False)
+
+ client = DefaultAsyncHttpxClient()
+
+ mounts = tuple(client._mounts.items())
+ assert len(mounts) == 1
+ assert mounts[0][0].pattern == "https://"
+
+ @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning")
+ async def test_default_client_creation(self) -> None:
+ # Ensure that the client can be initialized without any exceptions
+ DefaultAsyncHttpxClient(
+ verify=True,
+ cert=None,
+ trust_env=True,
+ http1=True,
+ http2=False,
+ limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
+ )
+
+ @pytest.mark.respx(base_url=base_url)
+ async def test_follow_redirects(self, respx_mock: MockRouter, async_client: AsyncOpencode) -> None:
+ # Test that the default follow_redirects=True allows following redirects
+ respx_mock.post("/redirect").mock(
+ return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
+ )
+ respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"}))
+
+ response = await async_client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
+ assert response.status_code == 200
+ assert response.json() == {"status": "ok"}
+
+ @pytest.mark.respx(base_url=base_url)
+ async def test_follow_redirects_disabled(self, respx_mock: MockRouter, async_client: AsyncOpencode) -> None:
+ # Test that follow_redirects=False prevents following redirects
+ respx_mock.post("/redirect").mock(
+ return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
+ )
+
+ with pytest.raises(APIStatusError) as exc_info:
+ await async_client.post(
+ "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response
+ )
+
+ assert exc_info.value.response.status_code == 302
+ assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected"
diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py
new file mode 100644
index 0000000..fefa314
--- /dev/null
+++ b/tests/test_extract_files.py
@@ -0,0 +1,91 @@
+from __future__ import annotations
+
+from typing import Sequence
+
+import pytest
+
+from opencode._types import FileTypes, ArrayFormat
+from opencode._utils import extract_files
+
+
+def test_removes_files_from_input() -> None:
+ query = {"foo": "bar"}
+ assert extract_files(query, paths=[]) == []
+ assert query == {"foo": "bar"}
+
+ query2 = {"foo": b"Bar", "hello": "world"}
+ assert extract_files(query2, paths=[["foo"]]) == [("foo", b"Bar")]
+ assert query2 == {"hello": "world"}
+
+ query3 = {"foo": {"foo": {"bar": b"Bar"}}, "hello": "world"}
+ assert extract_files(query3, paths=[["foo", "foo", "bar"]]) == [("foo[foo][bar]", b"Bar")]
+ assert query3 == {"foo": {"foo": {}}, "hello": "world"}
+
+ query4 = {"foo": {"bar": b"Bar", "baz": "foo"}, "hello": "world"}
+ assert extract_files(query4, paths=[["foo", "bar"]]) == [("foo[bar]", b"Bar")]
+ assert query4 == {"hello": "world", "foo": {"baz": "foo"}}
+
+
+def test_multiple_files() -> None:
+ query = {"documents": [{"file": b"My first file"}, {"file": b"My second file"}]}
+ assert extract_files(query, paths=[["documents", "", "file"]]) == [
+ ("documents[][file]", b"My first file"),
+ ("documents[][file]", b"My second file"),
+ ]
+ assert query == {"documents": [{}, {}]}
+
+
+def test_top_level_file_array() -> None:
+ query = {"files": [b"file one", b"file two"], "title": "hello"}
+ assert extract_files(query, paths=[["files", ""]]) == [("files[]", b"file one"), ("files[]", b"file two")]
+ assert query == {"title": "hello"}
+
+
+@pytest.mark.parametrize(
+ "query,paths,expected",
+ [
+ [
+ {"foo": {"bar": "baz"}},
+ [["foo", "", "bar"]],
+ [],
+ ],
+ [
+ {"foo": ["bar", "baz"]},
+ [["foo", "bar"]],
+ [],
+ ],
+ [
+ {"foo": {"bar": "baz"}},
+ [["foo", "foo"]],
+ [],
+ ],
+ ],
+ ids=["dict expecting array", "array expecting dict", "unknown keys"],
+)
+def test_ignores_incorrect_paths(
+ query: dict[str, object],
+ paths: Sequence[Sequence[str]],
+ expected: list[tuple[str, FileTypes]],
+) -> None:
+ assert extract_files(query, paths=paths) == expected
+
+
+@pytest.mark.parametrize(
+ "array_format,expected_top_level,expected_nested",
+ [
+ ("brackets", [("files[]", b"a"), ("files[]", b"b")], [("items[][file]", b"a"), ("items[][file]", b"b")]),
+ ("repeat", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]),
+ ("comma", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]),
+ ("indices", [("files[0]", b"a"), ("files[1]", b"b")], [("items[0][file]", b"a"), ("items[1][file]", b"b")]),
+ ],
+)
+def test_array_format_controls_file_field_names(
+ array_format: ArrayFormat,
+ expected_top_level: list[tuple[str, FileTypes]],
+ expected_nested: list[tuple[str, FileTypes]],
+) -> None:
+ top_level = {"files": [b"a", b"b"]}
+ assert extract_files(top_level, paths=[["files", ""]], array_format=array_format) == expected_top_level
+
+ nested = {"items": [{"file": b"a"}, {"file": b"b"}]}
+ assert extract_files(nested, paths=[["items", "", "file"]], array_format=array_format) == expected_nested
diff --git a/tests/test_files.py b/tests/test_files.py
new file mode 100644
index 0000000..dc1fd77
--- /dev/null
+++ b/tests/test_files.py
@@ -0,0 +1,148 @@
+from pathlib import Path
+
+import anyio
+import pytest
+from dirty_equals import IsDict, IsList, IsBytes, IsTuple
+
+from opencode._files import to_httpx_files, deepcopy_with_paths, async_to_httpx_files
+from opencode._utils import extract_files
+
+readme_path = Path(__file__).parent.parent.joinpath("README.md")
+
+
+def test_pathlib_includes_file_name() -> None:
+ result = to_httpx_files({"file": readme_path})
+ print(result)
+ assert result == IsDict({"file": IsTuple("README.md", IsBytes())})
+
+
+def test_tuple_input() -> None:
+ result = to_httpx_files([("file", readme_path)])
+ print(result)
+ assert result == IsList(IsTuple("file", IsTuple("README.md", IsBytes())))
+
+
+@pytest.mark.asyncio
+async def test_async_pathlib_includes_file_name() -> None:
+ result = await async_to_httpx_files({"file": readme_path})
+ print(result)
+ assert result == IsDict({"file": IsTuple("README.md", IsBytes())})
+
+
+@pytest.mark.asyncio
+async def test_async_supports_anyio_path() -> None:
+ result = await async_to_httpx_files({"file": anyio.Path(readme_path)})
+ print(result)
+ assert result == IsDict({"file": IsTuple("README.md", IsBytes())})
+
+
+@pytest.mark.asyncio
+async def test_async_tuple_input() -> None:
+ result = await async_to_httpx_files([("file", readme_path)])
+ print(result)
+ assert result == IsList(IsTuple("file", IsTuple("README.md", IsBytes())))
+
+
+def test_string_not_allowed() -> None:
+ with pytest.raises(TypeError, match="Expected file types input to be a FileContent type or to be a tuple"):
+ to_httpx_files(
+ {
+ "file": "foo", # type: ignore
+ }
+ )
+
+
+def assert_different_identities(obj1: object, obj2: object) -> None:
+ assert obj1 == obj2
+ assert obj1 is not obj2
+
+
+class TestDeepcopyWithPaths:
+ def test_copies_top_level_dict(self) -> None:
+ original = {"file": b"data", "other": "value"}
+ result = deepcopy_with_paths(original, [["file"]])
+ assert_different_identities(result, original)
+
+ def test_file_value_is_same_reference(self) -> None:
+ file_bytes = b"contents"
+ original = {"file": file_bytes}
+ result = deepcopy_with_paths(original, [["file"]])
+ assert_different_identities(result, original)
+ assert result["file"] is file_bytes
+
+ def test_list_popped_wholesale(self) -> None:
+ files = [b"f1", b"f2"]
+ original = {"files": files, "title": "t"}
+ result = deepcopy_with_paths(original, [["files", ""]])
+ assert_different_identities(result, original)
+ result_files = result["files"]
+ assert isinstance(result_files, list)
+ assert_different_identities(result_files, files)
+
+ def test_nested_array_path_copies_list_and_elements(self) -> None:
+ elem1 = {"file": b"f1", "extra": 1}
+ elem2 = {"file": b"f2", "extra": 2}
+ original = {"items": [elem1, elem2]}
+ result = deepcopy_with_paths(original, [["items", "", "file"]])
+ assert_different_identities(result, original)
+ result_items = result["items"]
+ assert isinstance(result_items, list)
+ assert_different_identities(result_items, original["items"])
+ assert_different_identities(result_items[0], elem1)
+ assert_different_identities(result_items[1], elem2)
+
+ def test_empty_paths_returns_same_object(self) -> None:
+ original = {"foo": "bar"}
+ result = deepcopy_with_paths(original, [])
+ assert result is original
+
+ def test_multiple_paths(self) -> None:
+ f1 = b"file1"
+ f2 = b"file2"
+ original = {"a": f1, "b": f2, "c": "unchanged"}
+ result = deepcopy_with_paths(original, [["a"], ["b"]])
+ assert_different_identities(result, original)
+ assert result["a"] is f1
+ assert result["b"] is f2
+ assert result["c"] is original["c"]
+
+ def test_extract_files_does_not_mutate_original_top_level(self) -> None:
+ file_bytes = b"contents"
+ original = {"file": file_bytes, "other": "value"}
+
+ copied = deepcopy_with_paths(original, [["file"]])
+ extracted = extract_files(copied, paths=[["file"]])
+
+ assert extracted == [("file", file_bytes)]
+ assert original == {"file": file_bytes, "other": "value"}
+ assert copied == {"other": "value"}
+
+ def test_extract_files_does_not_mutate_original_nested_array_path(self) -> None:
+ file1 = b"f1"
+ file2 = b"f2"
+ original = {
+ "items": [
+ {"file": file1, "extra": 1},
+ {"file": file2, "extra": 2},
+ ],
+ "title": "example",
+ }
+
+ copied = deepcopy_with_paths(original, [["items", "", "file"]])
+ extracted = extract_files(copied, paths=[["items", "", "file"]])
+
+ assert [entry for _, entry in extracted] == [file1, file2]
+ assert original == {
+ "items": [
+ {"file": file1, "extra": 1},
+ {"file": file2, "extra": 2},
+ ],
+ "title": "example",
+ }
+ assert copied == {
+ "items": [
+ {"extra": 1},
+ {"extra": 2},
+ ],
+ "title": "example",
+ }
diff --git a/tests/test_models.py b/tests/test_models.py
index 57fd820..b4944f3 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -1,77 +1,1017 @@
-from __future__ import annotations
+import json
+from typing import TYPE_CHECKING, Any, Dict, List, Union, Iterable, Optional, cast
+from datetime import datetime, timezone
+from collections import deque
+from typing_extensions import Literal, Annotated, TypedDict, TypeAliasType
+import pytest
import pydantic
+from pydantic import Field
-from opencode.types import (
- ConfigAgent,
- Event,
- ModelRef,
- PromptFileSourceInline,
- SessionMessageInfo,
- SessionMessageUser,
- UnknownEvent,
- UnknownSessionMessageInfo,
-)
+from opencode._utils import PropertyInfo
+from opencode._compat import PYDANTIC_V1, parse_obj, model_dump, model_json
+from opencode._models import DISCRIMINATOR_CACHE, BaseModel, EagerIterable, construct_type
+
+
+class BasicModel(BaseModel):
+ foo: str
+
+
+@pytest.mark.parametrize("value", ["hello", 1], ids=["correct type", "mismatched"])
+def test_basic(value: object) -> None:
+ m = BasicModel.construct(foo=value)
+ assert m.foo == value
+
+
+def test_directly_nested_model() -> None:
+ class NestedModel(BaseModel):
+ nested: BasicModel
+
+ m = NestedModel.construct(nested={"foo": "Foo!"})
+ assert m.nested.foo == "Foo!"
+
+ # mismatched types
+ m = NestedModel.construct(nested="hello!")
+ assert cast(Any, m.nested) == "hello!"
+
+
+def test_optional_nested_model() -> None:
+ class NestedModel(BaseModel):
+ nested: Optional[BasicModel]
+
+ m1 = NestedModel.construct(nested=None)
+ assert m1.nested is None
+
+ m2 = NestedModel.construct(nested={"foo": "bar"})
+ assert m2.nested is not None
+ assert m2.nested.foo == "bar"
+
+ # mismatched types
+ m3 = NestedModel.construct(nested={"foo"})
+ assert isinstance(cast(Any, m3.nested), set)
+ assert cast(Any, m3.nested) == {"foo"}
+
+
+def test_list_nested_model() -> None:
+ class NestedModel(BaseModel):
+ nested: List[BasicModel]
+
+ m = NestedModel.construct(nested=[{"foo": "bar"}, {"foo": "2"}])
+ assert m.nested is not None
+ assert isinstance(m.nested, list)
+ assert len(m.nested) == 2
+ assert m.nested[0].foo == "bar"
+ assert m.nested[1].foo == "2"
+
+ # mismatched types
+ m = NestedModel.construct(nested=True)
+ assert cast(Any, m.nested) is True
+
+ m = NestedModel.construct(nested=[False])
+ assert cast(Any, m.nested) == [False]
+
+
+def test_optional_list_nested_model() -> None:
+ class NestedModel(BaseModel):
+ nested: Optional[List[BasicModel]]
+
+ m1 = NestedModel.construct(nested=[{"foo": "bar"}, {"foo": "2"}])
+ assert m1.nested is not None
+ assert isinstance(m1.nested, list)
+ assert len(m1.nested) == 2
+ assert m1.nested[0].foo == "bar"
+ assert m1.nested[1].foo == "2"
+
+ m2 = NestedModel.construct(nested=None)
+ assert m2.nested is None
+
+ # mismatched types
+ m3 = NestedModel.construct(nested={1})
+ assert cast(Any, m3.nested) == {1}
+
+ m4 = NestedModel.construct(nested=[False])
+ assert cast(Any, m4.nested) == [False]
+
+
+def test_list_optional_items_nested_model() -> None:
+ class NestedModel(BaseModel):
+ nested: List[Optional[BasicModel]]
+
+ m = NestedModel.construct(nested=[None, {"foo": "bar"}])
+ assert m.nested is not None
+ assert isinstance(m.nested, list)
+ assert len(m.nested) == 2
+ assert m.nested[0] is None
+ assert m.nested[1] is not None
+ assert m.nested[1].foo == "bar"
+
+ # mismatched types
+ m3 = NestedModel.construct(nested="foo")
+ assert cast(Any, m3.nested) == "foo"
+
+ m4 = NestedModel.construct(nested=[False])
+ assert cast(Any, m4.nested) == [False]
+
+
+def test_list_mismatched_type() -> None:
+ class NestedModel(BaseModel):
+ nested: List[str]
+
+ m = NestedModel.construct(nested=False)
+ assert cast(Any, m.nested) is False
+
+
+def test_raw_dictionary() -> None:
+ class NestedModel(BaseModel):
+ nested: Dict[str, str]
+
+ m = NestedModel.construct(nested={"hello": "world"})
+ assert m.nested == {"hello": "world"}
+
+ # mismatched types
+ m = NestedModel.construct(nested=False)
+ assert cast(Any, m.nested) is False
+
+
+def test_nested_dictionary_model() -> None:
+ class NestedModel(BaseModel):
+ nested: Dict[str, BasicModel]
+
+ m = NestedModel.construct(nested={"hello": {"foo": "bar"}})
+ assert isinstance(m.nested, dict)
+ assert m.nested["hello"].foo == "bar"
+
+ # mismatched types
+ m = NestedModel.construct(nested={"hello": False})
+ assert cast(Any, m.nested["hello"]) is False
+
+
+def test_unknown_fields() -> None:
+ m1 = BasicModel.construct(foo="foo", unknown=1)
+ assert m1.foo == "foo"
+ assert cast(Any, m1).unknown == 1
+
+ m2 = BasicModel.construct(foo="foo", unknown={"foo_bar": True})
+ assert m2.foo == "foo"
+ assert cast(Any, m2).unknown == {"foo_bar": True}
+
+ assert model_dump(m2) == {"foo": "foo", "unknown": {"foo_bar": True}}
+
+
+def test_strict_validation_unknown_fields() -> None:
+ class Model(BaseModel):
+ foo: str
+
+ model = parse_obj(Model, dict(foo="hello!", user="Robert"))
+ assert model.foo == "hello!"
+ assert cast(Any, model).user == "Robert"
+
+ assert model_dump(model) == {"foo": "hello!", "user": "Robert"}
+
+
+def test_aliases() -> None:
+ class Model(BaseModel):
+ my_field: int = Field(alias="myField")
+
+ m = Model.construct(myField=1)
+ assert m.my_field == 1
+
+ # mismatched types
+ m = Model.construct(myField={"hello": False})
+ assert cast(Any, m.my_field) == {"hello": False}
+
+
+def test_repr() -> None:
+ model = BasicModel(foo="bar")
+ assert str(model) == "BasicModel(foo='bar')"
+ assert repr(model) == "BasicModel(foo='bar')"
+
+
+def test_repr_nested_model() -> None:
+ class Child(BaseModel):
+ name: str
+ age: int
+
+ class Parent(BaseModel):
+ name: str
+ child: Child
+
+ model = Parent(name="Robert", child=Child(name="Foo", age=5))
+ assert str(model) == "Parent(name='Robert', child=Child(name='Foo', age=5))"
+ assert repr(model) == "Parent(name='Robert', child=Child(name='Foo', age=5))"
+
+
+def test_optional_list() -> None:
+ class Submodel(BaseModel):
+ name: str
+
+ class Model(BaseModel):
+ items: Optional[List[Submodel]]
+
+ m = Model.construct(items=None)
+ assert m.items is None
+
+ m = Model.construct(items=[])
+ assert m.items == []
+
+ m = Model.construct(items=[{"name": "Robert"}])
+ assert m.items is not None
+ assert len(m.items) == 1
+ assert m.items[0].name == "Robert"
+
+
+def test_nested_union_of_models() -> None:
+ class Submodel1(BaseModel):
+ bar: bool
+
+ class Submodel2(BaseModel):
+ thing: str
+
+ class Model(BaseModel):
+ foo: Union[Submodel1, Submodel2]
+
+ m = Model.construct(foo={"thing": "hello"})
+ assert isinstance(m.foo, Submodel2)
+ assert m.foo.thing == "hello"
+
+
+def test_nested_union_of_mixed_types() -> None:
+ class Submodel1(BaseModel):
+ bar: bool
+
+ class Model(BaseModel):
+ foo: Union[Submodel1, Literal[True], Literal["CARD_HOLDER"]]
+
+ m = Model.construct(foo=True)
+ assert m.foo is True
+
+ m = Model.construct(foo="CARD_HOLDER")
+ assert m.foo == "CARD_HOLDER"
+
+ m = Model.construct(foo={"bar": False})
+ assert isinstance(m.foo, Submodel1)
+ assert m.foo.bar is False
+
+
+def test_nested_union_multiple_variants() -> None:
+ class Submodel1(BaseModel):
+ bar: bool
+
+ class Submodel2(BaseModel):
+ thing: str
+
+ class Submodel3(BaseModel):
+ foo: int
+
+ class Model(BaseModel):
+ foo: Union[Submodel1, Submodel2, None, Submodel3]
+
+ m = Model.construct(foo={"thing": "hello"})
+ assert isinstance(m.foo, Submodel2)
+ assert m.foo.thing == "hello"
+
+ m = Model.construct(foo=None)
+ assert m.foo is None
+
+ m = Model.construct()
+ assert m.foo is None
+
+ m = Model.construct(foo={"foo": "1"})
+ assert isinstance(m.foo, Submodel3)
+ assert m.foo.foo == 1
+
+
+def test_nested_union_invalid_data() -> None:
+ class Submodel1(BaseModel):
+ level: int
+
+ class Submodel2(BaseModel):
+ name: str
+
+ class Model(BaseModel):
+ foo: Union[Submodel1, Submodel2]
+
+ m = Model.construct(foo=True)
+ assert cast(bool, m.foo) is True
+
+ m = Model.construct(foo={"name": 3})
+ if PYDANTIC_V1:
+ assert isinstance(m.foo, Submodel2)
+ assert m.foo.name == "3"
+ else:
+ assert isinstance(m.foo, Submodel1)
+ assert m.foo.name == 3 # type: ignore
+
+
+def test_list_of_unions() -> None:
+ class Submodel1(BaseModel):
+ level: int
+
+ class Submodel2(BaseModel):
+ name: str
+
+ class Model(BaseModel):
+ items: List[Union[Submodel1, Submodel2]]
+
+ m = Model.construct(items=[{"level": 1}, {"name": "Robert"}])
+ assert len(m.items) == 2
+ assert isinstance(m.items[0], Submodel1)
+ assert m.items[0].level == 1
+ assert isinstance(m.items[1], Submodel2)
+ assert m.items[1].name == "Robert"
+
+ m = Model.construct(items=[{"level": -1}, 156])
+ assert len(m.items) == 2
+ assert isinstance(m.items[0], Submodel1)
+ assert m.items[0].level == -1
+ assert cast(Any, m.items[1]) == 156
+
+
+def test_union_of_lists() -> None:
+ class SubModel1(BaseModel):
+ level: int
+
+ class SubModel2(BaseModel):
+ name: str
+
+ class Model(BaseModel):
+ items: Union[List[SubModel1], List[SubModel2]]
+
+ # with one valid entry
+ m = Model.construct(items=[{"name": "Robert"}])
+ assert len(m.items) == 1
+ assert isinstance(m.items[0], SubModel2)
+ assert m.items[0].name == "Robert"
+
+ # with two entries pointing to different types
+ m = Model.construct(items=[{"level": 1}, {"name": "Robert"}])
+ assert len(m.items) == 2
+ assert isinstance(m.items[0], SubModel1)
+ assert m.items[0].level == 1
+ assert isinstance(m.items[1], SubModel1)
+ assert cast(Any, m.items[1]).name == "Robert"
+
+ # with two entries pointing to *completely* different types
+ m = Model.construct(items=[{"level": -1}, 156])
+ assert len(m.items) == 2
+ assert isinstance(m.items[0], SubModel1)
+ assert m.items[0].level == -1
+ assert cast(Any, m.items[1]) == 156
+
+
+def test_dict_of_union() -> None:
+ class SubModel1(BaseModel):
+ name: str
+
+ class SubModel2(BaseModel):
+ foo: str
+
+ class Model(BaseModel):
+ data: Dict[str, Union[SubModel1, SubModel2]]
+
+ m = Model.construct(data={"hello": {"name": "there"}, "foo": {"foo": "bar"}})
+ assert len(list(m.data.keys())) == 2
+ assert isinstance(m.data["hello"], SubModel1)
+ assert m.data["hello"].name == "there"
+ assert isinstance(m.data["foo"], SubModel2)
+ assert m.data["foo"].foo == "bar"
+
+ # TODO: test mismatched type
+
+
+def test_double_nested_union() -> None:
+ class SubModel1(BaseModel):
+ name: str
+
+ class SubModel2(BaseModel):
+ bar: str
+
+ class Model(BaseModel):
+ data: Dict[str, List[Union[SubModel1, SubModel2]]]
+
+ m = Model.construct(data={"foo": [{"bar": "baz"}, {"name": "Robert"}]})
+ assert len(m.data["foo"]) == 2
+
+ entry1 = m.data["foo"][0]
+ assert isinstance(entry1, SubModel2)
+ assert entry1.bar == "baz"
+
+ entry2 = m.data["foo"][1]
+ assert isinstance(entry2, SubModel1)
+ assert entry2.name == "Robert"
+
+ # TODO: test mismatched type
+
+
+def test_union_of_dict() -> None:
+ class SubModel1(BaseModel):
+ name: str
+
+ class SubModel2(BaseModel):
+ foo: str
+
+ class Model(BaseModel):
+ data: Union[Dict[str, SubModel1], Dict[str, SubModel2]]
+
+ m = Model.construct(data={"hello": {"name": "there"}, "foo": {"foo": "bar"}})
+ assert len(list(m.data.keys())) == 2
+ assert isinstance(m.data["hello"], SubModel1)
+ assert m.data["hello"].name == "there"
+ assert isinstance(m.data["foo"], SubModel1)
+ assert cast(Any, m.data["foo"]).foo == "bar"
+
+
+def test_iso8601_datetime() -> None:
+ class Model(BaseModel):
+ created_at: datetime
+
+ expected = datetime(2019, 12, 27, 18, 11, 19, 117000, tzinfo=timezone.utc)
+
+ if PYDANTIC_V1:
+ expected_json = '{"created_at": "2019-12-27T18:11:19.117000+00:00"}'
+ else:
+ expected_json = '{"created_at":"2019-12-27T18:11:19.117000Z"}'
+
+ model = Model.construct(created_at="2019-12-27T18:11:19.117Z")
+ assert model.created_at == expected
+ assert model_json(model) == expected_json
+
+ model = parse_obj(Model, dict(created_at="2019-12-27T18:11:19.117Z"))
+ assert model.created_at == expected
+ assert model_json(model) == expected_json
+
+
+def test_does_not_coerce_int() -> None:
+ class Model(BaseModel):
+ bar: int
+
+ assert Model.construct(bar=1).bar == 1
+ assert Model.construct(bar=10.9).bar == 10.9
+ assert Model.construct(bar="19").bar == "19" # type: ignore[comparison-overlap]
+ assert Model.construct(bar=False).bar is False
+
+
+def test_int_to_float_safe_conversion() -> None:
+ class Model(BaseModel):
+ float_field: float
+
+ m = Model.construct(float_field=10)
+ assert m.float_field == 10.0
+ assert isinstance(m.float_field, float)
+
+ m = Model.construct(float_field=10.12)
+ assert m.float_field == 10.12
+ assert isinstance(m.float_field, float)
-MESSAGES: pydantic.TypeAdapter[SessionMessageInfo] = pydantic.TypeAdapter(SessionMessageInfo)
-EVENTS: pydantic.TypeAdapter[Event] = pydantic.TypeAdapter(Event)
+ # number too big
+ m = Model.construct(float_field=2**53 + 1)
+ assert m.float_field == 2**53 + 1
+ assert isinstance(m.float_field, int)
-def test_models_accept_wire_and_python_names() -> None:
- assert ModelRef.model_validate({"id": "m", "providerID": "p"}).provider_id == "p"
- assert ModelRef(id="m", provider_id="p").to_dict() == {"id": "m", "providerID": "p"}
+def test_deprecated_alias() -> None:
+ class Model(BaseModel):
+ resource_id: str = Field(alias="model_id")
+ @property
+ def model_id(self) -> str:
+ return self.resource_id
-def test_optional_fields_are_omitted_not_null() -> None:
- assert ModelRef(id="m", provider_id="p", variant=None).to_dict() == {"id": "m", "providerID": "p"}
+ m = Model.construct(model_id="id")
+ assert m.model_id == "id"
+ assert m.resource_id == "id"
+ assert m.resource_id is m.model_id
+ m = parse_obj(Model, {"model_id": "id"})
+ assert m.model_id == "id"
+ assert m.resource_id == "id"
+ assert m.resource_id is m.model_id
-def test_unknown_fields_are_kept() -> None:
- ref = ModelRef.model_validate({"id": "m", "providerID": "p", "newField": 1})
- assert ref.model_extra == {"newField": 1}
+def test_omitted_fields() -> None:
+ class Model(BaseModel):
+ resource_id: Optional[str] = None
-def test_union_tag_defaults_and_is_serialized() -> None:
- source = PromptFileSourceInline()
- assert source.to_dict() == {"type": "inline"}
- assert PromptFileSourceInline.model_validate({"type": "inline"}).to_dict() == {"type": "inline"}
+ m = Model.construct()
+ assert m.resource_id is None
+ assert "resource_id" not in m.model_fields_set
+ m = Model.construct(resource_id=None)
+ assert m.resource_id is None
+ assert "resource_id" in m.model_fields_set
-def test_discriminated_union_selects_member() -> None:
- message = MESSAGES.validate_python({"type": "user", "id": "msg_1", "time": {"created": 1}, "text": "hello"})
- assert isinstance(message, SessionMessageUser)
- assert message.text == "hello"
+ m = Model.construct(resource_id="foo")
+ assert m.resource_id == "foo"
+ assert "resource_id" in m.model_fields_set
-def test_unknown_union_variant_falls_back_instead_of_failing() -> None:
- message = MESSAGES.validate_python({"type": "hologram", "id": "msg_2", "payload": [1, 2]})
- assert isinstance(message, UnknownSessionMessageInfo)
- assert message.type == "hologram"
- assert message.id == "msg_2"
- assert message.model_extra == {"payload": [1, 2]}
+def test_to_dict() -> None:
+ class Model(BaseModel):
+ foo: Optional[str] = Field(alias="FOO", default=None)
+ m = Model(FOO="hello")
+ assert m.to_dict() == {"FOO": "hello"}
+ assert m.to_dict(use_api_names=False) == {"foo": "hello"}
-def test_unknown_event_type_falls_back() -> None:
- event = EVENTS.validate_python({"type": "future.thing", "id": "evt_1", "created": 1, "data": {}})
- assert isinstance(event, UnknownEvent)
+ m2 = Model()
+ assert m2.to_dict() == {}
+ assert m2.to_dict(exclude_unset=False) == {"FOO": None}
+ assert m2.to_dict(exclude_unset=False, exclude_none=True) == {}
+ assert m2.to_dict(exclude_unset=False, exclude_defaults=True) == {}
+ m3 = Model(FOO=None)
+ assert m3.to_dict() == {"FOO": None}
+ assert m3.to_dict(exclude_none=True) == {}
+ assert m3.to_dict(exclude_defaults=True) == {}
-def test_rpc_events_match_by_pattern() -> None:
- event = EVENTS.validate_python(
- {"type": "rpc.myplugin.ping", "id": "evt_1", "created": 1, "location": {"directory": "/r"}, "data": {}}
+ class Model2(BaseModel):
+ created_at: datetime
+
+ time_str = "2024-03-21T11:39:01.275859"
+ m4 = Model2.construct(created_at=time_str)
+ assert m4.to_dict(mode="python") == {"created_at": datetime.fromisoformat(time_str)}
+ assert m4.to_dict(mode="json") == {"created_at": time_str}
+
+ if PYDANTIC_V1:
+ with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"):
+ m.to_dict(warnings=False)
+
+
+def test_forwards_compat_model_dump_method() -> None:
+ class Model(BaseModel):
+ foo: Optional[str] = Field(alias="FOO", default=None)
+
+ m = Model(FOO="hello")
+ assert m.model_dump() == {"foo": "hello"}
+ assert m.model_dump(include={"bar"}) == {}
+ assert m.model_dump(exclude={"foo"}) == {}
+ assert m.model_dump(by_alias=True) == {"FOO": "hello"}
+
+ m2 = Model()
+ assert m2.model_dump() == {"foo": None}
+ assert m2.model_dump(exclude_unset=True) == {}
+ assert m2.model_dump(exclude_none=True) == {}
+ assert m2.model_dump(exclude_defaults=True) == {}
+
+ m3 = Model(FOO=None)
+ assert m3.model_dump() == {"foo": None}
+ assert m3.model_dump(exclude_none=True) == {}
+
+ if PYDANTIC_V1:
+ with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"):
+ m.model_dump(round_trip=True)
+
+ with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"):
+ m.model_dump(warnings=False)
+
+
+def test_compat_method_no_error_for_warnings() -> None:
+ class Model(BaseModel):
+ foo: Optional[str]
+
+ m = Model(foo="hello")
+ assert isinstance(model_dump(m, warnings=False), dict)
+
+
+def test_to_json() -> None:
+ class Model(BaseModel):
+ foo: Optional[str] = Field(alias="FOO", default=None)
+
+ m = Model(FOO="hello")
+ assert json.loads(m.to_json()) == {"FOO": "hello"}
+ assert json.loads(m.to_json(use_api_names=False)) == {"foo": "hello"}
+
+ if PYDANTIC_V1:
+ assert m.to_json(indent=None) == '{"FOO": "hello"}'
+ else:
+ assert m.to_json(indent=None) == '{"FOO":"hello"}'
+
+ m2 = Model()
+ assert json.loads(m2.to_json()) == {}
+ assert json.loads(m2.to_json(exclude_unset=False)) == {"FOO": None}
+ assert json.loads(m2.to_json(exclude_unset=False, exclude_none=True)) == {}
+ assert json.loads(m2.to_json(exclude_unset=False, exclude_defaults=True)) == {}
+
+ m3 = Model(FOO=None)
+ assert json.loads(m3.to_json()) == {"FOO": None}
+ assert json.loads(m3.to_json(exclude_none=True)) == {}
+
+ if PYDANTIC_V1:
+ with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"):
+ m.to_json(warnings=False)
+
+
+def test_forwards_compat_model_dump_json_method() -> None:
+ class Model(BaseModel):
+ foo: Optional[str] = Field(alias="FOO", default=None)
+
+ m = Model(FOO="hello")
+ assert json.loads(m.model_dump_json()) == {"foo": "hello"}
+ assert json.loads(m.model_dump_json(include={"bar"})) == {}
+ assert json.loads(m.model_dump_json(include={"foo"})) == {"foo": "hello"}
+ assert json.loads(m.model_dump_json(by_alias=True)) == {"FOO": "hello"}
+
+ assert m.model_dump_json(indent=2) == '{\n "foo": "hello"\n}'
+
+ m2 = Model()
+ assert json.loads(m2.model_dump_json()) == {"foo": None}
+ assert json.loads(m2.model_dump_json(exclude_unset=True)) == {}
+ assert json.loads(m2.model_dump_json(exclude_none=True)) == {}
+ assert json.loads(m2.model_dump_json(exclude_defaults=True)) == {}
+
+ m3 = Model(FOO=None)
+ assert json.loads(m3.model_dump_json()) == {"foo": None}
+ assert json.loads(m3.model_dump_json(exclude_none=True)) == {}
+
+ if PYDANTIC_V1:
+ with pytest.raises(ValueError, match="round_trip is only supported in Pydantic v2"):
+ m.model_dump_json(round_trip=True)
+
+ with pytest.raises(ValueError, match="warnings is only supported in Pydantic v2"):
+ m.model_dump_json(warnings=False)
+
+
+def test_type_compat() -> None:
+ # our model type can be assigned to Pydantic's model type
+
+ def takes_pydantic(model: pydantic.BaseModel) -> None: # noqa: ARG001
+ ...
+
+ class OurModel(BaseModel):
+ foo: Optional[str] = None
+
+ takes_pydantic(OurModel())
+
+
+def test_annotated_types() -> None:
+ class Model(BaseModel):
+ value: str
+
+ m = construct_type(
+ value={"value": "foo"},
+ type_=cast(Any, Annotated[Model, "random metadata"]),
+ )
+ assert isinstance(m, Model)
+ assert m.value == "foo"
+
+
+def test_discriminated_unions_invalid_data() -> None:
+ class A(BaseModel):
+ type: Literal["a"]
+
+ data: str
+
+ class B(BaseModel):
+ type: Literal["b"]
+
+ data: int
+
+ m = construct_type(
+ value={"type": "b", "data": "foo"},
+ type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]),
+ )
+ assert isinstance(m, B)
+ assert m.type == "b"
+ assert m.data == "foo" # type: ignore[comparison-overlap]
+
+ m = construct_type(
+ value={"type": "a", "data": 100},
+ type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]),
+ )
+ assert isinstance(m, A)
+ assert m.type == "a"
+ if PYDANTIC_V1:
+ # pydantic v1 automatically converts inputs to strings
+ # if the expected type is a str
+ assert m.data == "100"
+ else:
+ assert m.data == 100 # type: ignore[comparison-overlap]
+
+
+def test_discriminated_unions_unknown_variant() -> None:
+ class A(BaseModel):
+ type: Literal["a"]
+
+ data: str
+
+ class B(BaseModel):
+ type: Literal["b"]
+
+ data: int
+
+ m = construct_type(
+ value={"type": "c", "data": None, "new_thing": "bar"},
+ type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]),
)
- assert type(event).__name__ == "RpcEvent"
+
+ # just chooses the first variant
+ assert isinstance(m, A)
+ assert m.type == "c" # type: ignore[comparison-overlap]
+ assert m.data == None # type: ignore[unreachable]
+ assert m.new_thing == "bar"
+
+
+def test_discriminated_unions_invalid_data_nested_unions() -> None:
+ class A(BaseModel):
+ type: Literal["a"]
+
+ data: str
+
+ class B(BaseModel):
+ type: Literal["b"]
+
+ data: int
+
+ class C(BaseModel):
+ type: Literal["c"]
+
+ data: bool
+
+ m = construct_type(
+ value={"type": "b", "data": "foo"},
+ type_=cast(Any, Annotated[Union[Union[A, B], C], PropertyInfo(discriminator="type")]),
+ )
+ assert isinstance(m, B)
+ assert m.type == "b"
+ assert m.data == "foo" # type: ignore[comparison-overlap]
+
+ m = construct_type(
+ value={"type": "c", "data": "foo"},
+ type_=cast(Any, Annotated[Union[Union[A, B], C], PropertyInfo(discriminator="type")]),
+ )
+ assert isinstance(m, C)
+ assert m.type == "c"
+ assert m.data == "foo" # type: ignore[comparison-overlap]
+
+
+def test_discriminated_unions_with_aliases_invalid_data() -> None:
+ class A(BaseModel):
+ foo_type: Literal["a"] = Field(alias="type")
+
+ data: str
+
+ class B(BaseModel):
+ foo_type: Literal["b"] = Field(alias="type")
+
+ data: int
+
+ m = construct_type(
+ value={"type": "b", "data": "foo"},
+ type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="foo_type")]),
+ )
+ assert isinstance(m, B)
+ assert m.foo_type == "b"
+ assert m.data == "foo" # type: ignore[comparison-overlap]
+
+ m = construct_type(
+ value={"type": "a", "data": 100},
+ type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="foo_type")]),
+ )
+ assert isinstance(m, A)
+ assert m.foo_type == "a"
+ if PYDANTIC_V1:
+ # pydantic v1 automatically converts inputs to strings
+ # if the expected type is a str
+ assert m.data == "100"
+ else:
+ assert m.data == 100 # type: ignore[comparison-overlap]
+
+
+def test_discriminated_unions_overlapping_discriminators_invalid_data() -> None:
+ class A(BaseModel):
+ type: Literal["a"]
+
+ data: bool
+
+ class B(BaseModel):
+ type: Literal["a"]
+
+ data: int
+
+ m = construct_type(
+ value={"type": "a", "data": "foo"},
+ type_=cast(Any, Annotated[Union[A, B], PropertyInfo(discriminator="type")]),
+ )
+ assert isinstance(m, B)
+ assert m.type == "a"
+ assert m.data == "foo" # type: ignore[comparison-overlap]
+
+
+def test_discriminated_unions_invalid_data_uses_cache() -> None:
+ class A(BaseModel):
+ type: Literal["a"]
+
+ data: str
+
+ class B(BaseModel):
+ type: Literal["b"]
+
+ data: int
+
+ UnionType = cast(Any, Union[A, B])
+
+ assert not DISCRIMINATOR_CACHE.get(UnionType)
+
+ m = construct_type(
+ value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")])
+ )
+ assert isinstance(m, B)
+ assert m.type == "b"
+ assert m.data == "foo" # type: ignore[comparison-overlap]
+
+ discriminator = DISCRIMINATOR_CACHE.get(UnionType)
+ assert discriminator is not None
+
+ m = construct_type(
+ value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")])
+ )
+ assert isinstance(m, B)
+ assert m.type == "b"
+ assert m.data == "foo" # type: ignore[comparison-overlap]
+
+ # if the discriminator details object stays the same between invocations then
+ # we hit the cache
+ assert DISCRIMINATOR_CACHE.get(UnionType) is discriminator
+
+
+@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1")
+def test_type_alias_type() -> None:
+ Alias = TypeAliasType("Alias", str) # pyright: ignore
+
+ class Model(BaseModel):
+ alias: Alias
+ union: Union[int, Alias]
+
+ m = construct_type(value={"alias": "foo", "union": "bar"}, type_=Model)
+ assert isinstance(m, Model)
+ assert isinstance(m.alias, str)
+ assert m.alias == "foo"
+ assert isinstance(m.union, str)
+ assert m.union == "bar"
+
+
+@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1")
+def test_field_named_cls() -> None:
+ class Model(BaseModel):
+ cls: str
+
+ m = construct_type(value={"cls": "foo"}, type_=Model)
+ assert isinstance(m, Model)
+ assert isinstance(m.cls, str)
+
+
+def test_discriminated_union_case() -> None:
+ class A(BaseModel):
+ type: Literal["a"]
+
+ data: bool
+
+ class B(BaseModel):
+ type: Literal["b"]
+
+ data: List[Union[A, object]]
+
+ class ModelA(BaseModel):
+ type: Literal["modelA"]
+
+ data: int
+
+ class ModelB(BaseModel):
+ type: Literal["modelB"]
+
+ required: str
+
+ data: Union[A, B]
+
+ # when constructing ModelA | ModelB, value data doesn't match ModelB exactly - missing `required`
+ m = construct_type(
+ value={"type": "modelB", "data": {"type": "a", "data": True}},
+ type_=cast(Any, Annotated[Union[ModelA, ModelB], PropertyInfo(discriminator="type")]),
+ )
+
+ assert isinstance(m, ModelB)
+
+
+def test_nested_discriminated_union() -> None:
+ class InnerType1(BaseModel):
+ type: Literal["type_1"]
+
+ class InnerModel(BaseModel):
+ inner_value: str
+
+ class InnerType2(BaseModel):
+ type: Literal["type_2"]
+ some_inner_model: InnerModel
+
+ class Type1(BaseModel):
+ base_type: Literal["base_type_1"]
+ value: Annotated[
+ Union[
+ InnerType1,
+ InnerType2,
+ ],
+ PropertyInfo(discriminator="type"),
+ ]
+
+ class Type2(BaseModel):
+ base_type: Literal["base_type_2"]
+
+ T = Annotated[
+ Union[
+ Type1,
+ Type2,
+ ],
+ PropertyInfo(discriminator="base_type"),
+ ]
+
+ model = construct_type(
+ type_=T,
+ value={
+ "base_type": "base_type_1",
+ "value": {
+ "type": "type_2",
+ },
+ },
+ )
+ assert isinstance(model, Type1)
+ assert isinstance(model.value, InnerType2)
+
+
+@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2 for now")
+def test_extra_properties() -> None:
+ class Item(BaseModel):
+ prop: int
+
+ class Model(BaseModel):
+ __pydantic_extra__: Dict[str, Item] = Field(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ other: str
+
+ if TYPE_CHECKING:
+
+ def __getattr__(self, attr: str) -> Item: ...
+
+ model = construct_type(
+ type_=Model,
+ value={
+ "a": {"prop": 1},
+ "other": "foo",
+ },
+ )
+ assert isinstance(model, Model)
+ assert model.a.prop == 1
+ assert isinstance(model.a, Item)
+ assert model.other == "foo"
+
+
+# NOTE: Workaround for Pydantic Iterable behavior.
+# Iterable fields are replaced with a ValidatorIterator and may be consumed
+# during serialization, which can cause subsequent dumps to return empty data.
+# See: https://github.com/pydantic/pydantic/issues/9541
+@pytest.mark.parametrize(
+ "data, expected_validated",
+ [
+ ([1, 2, 3], [1, 2, 3]),
+ ((1, 2, 3), (1, 2, 3)),
+ (set([1, 2, 3]), set([1, 2, 3])),
+ (iter([1, 2, 3]), [1, 2, 3]),
+ ([], []),
+ ((x for x in [1, 2, 3]), [1, 2, 3]),
+ (map(lambda x: x, [1, 2, 3]), [1, 2, 3]),
+ (frozenset([1, 2, 3]), frozenset([1, 2, 3])),
+ (deque([1, 2, 3]), deque([1, 2, 3])),
+ ],
+ ids=["list", "tuple", "set", "iterator", "empty", "generator", "map", "frozenset", "deque"],
+)
+@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2")
+def test_iterable_construction(data: Iterable[int], expected_validated: Iterable[int]) -> None:
+ class TypeWithIterable(TypedDict):
+ items: EagerIterable[int]
+
+ class Model(BaseModel):
+ data: TypeWithIterable
+
+ m = Model.model_validate({"data": {"items": data}})
+ assert m.data["items"] == expected_validated
+
+ # Verify repeated dumps don't lose data (the original bug)
+ assert m.model_dump()["data"]["items"] == list(expected_validated)
+ assert m.model_dump()["data"]["items"] == list(expected_validated)
-def test_open_enum_accepts_new_values() -> None:
- assert ConfigAgent.model_validate({"mode": "primary"}).mode == "primary"
- assert ConfigAgent.model_validate({"mode": "orchestrator"}).mode == "orchestrator"
+@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2")
+def test_iterable_construction_str_falls_back_to_list() -> None:
+ # str is iterable (over chars), but str(list_of_chars) produces the list's repr
+ # rather than reconstructing a string from items. We special-case str to fall
+ # back to list instead of attempting reconstruction.
+ class TypeWithIterable(TypedDict):
+ items: EagerIterable[str]
+ class Model(BaseModel):
+ data: TypeWithIterable
-def test_import_does_not_build_models() -> None:
- import subprocess
- import sys
+ m = Model.model_validate({"data": {"items": "hello"}})
- code = "import sys, opencode; opencode.Opencode('http://x'); print('opencode.types._generated' in sys.modules)"
- result = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True, check=True)
- assert result.stdout.strip() == "False"
+ # falls back to list of chars rather than calling str(["h", "e", "l", "l", "o"])
+ assert m.data["items"] == ["h", "e", "l", "l", "o"]
+ assert m.model_dump()["data"]["items"] == ["h", "e", "l", "l", "o"]
diff --git a/tests/test_qs.py b/tests/test_qs.py
new file mode 100644
index 0000000..ce566b0
--- /dev/null
+++ b/tests/test_qs.py
@@ -0,0 +1,78 @@
+from typing import Any, cast
+from functools import partial
+from urllib.parse import unquote
+
+import pytest
+
+from opencode._qs import Querystring, stringify
+
+
+def test_empty() -> None:
+ assert stringify({}) == ""
+ assert stringify({"a": {}}) == ""
+ assert stringify({"a": {"b": {"c": {}}}}) == ""
+
+
+def test_basic() -> None:
+ assert stringify({"a": 1}) == "a=1"
+ assert stringify({"a": "b"}) == "a=b"
+ assert stringify({"a": True}) == "a=true"
+ assert stringify({"a": False}) == "a=false"
+ assert stringify({"a": 1.23456}) == "a=1.23456"
+ assert stringify({"a": None}) == ""
+
+
+@pytest.mark.parametrize("method", ["class", "function"])
+def test_nested_dotted(method: str) -> None:
+ if method == "class":
+ serialise = Querystring(nested_format="dots").stringify
+ else:
+ serialise = partial(stringify, nested_format="dots")
+
+ assert unquote(serialise({"a": {"b": "c"}})) == "a.b=c"
+ assert unquote(serialise({"a": {"b": "c", "d": "e", "f": "g"}})) == "a.b=c&a.d=e&a.f=g"
+ assert unquote(serialise({"a": {"b": {"c": {"d": "e"}}}})) == "a.b.c.d=e"
+ assert unquote(serialise({"a": {"b": True}})) == "a.b=true"
+
+
+def test_nested_brackets() -> None:
+ assert unquote(stringify({"a": {"b": "c"}})) == "a[b]=c"
+ assert unquote(stringify({"a": {"b": "c", "d": "e", "f": "g"}})) == "a[b]=c&a[d]=e&a[f]=g"
+ assert unquote(stringify({"a": {"b": {"c": {"d": "e"}}}})) == "a[b][c][d]=e"
+ assert unquote(stringify({"a": {"b": True}})) == "a[b]=true"
+
+
+@pytest.mark.parametrize("method", ["class", "function"])
+def test_array_comma(method: str) -> None:
+ if method == "class":
+ serialise = Querystring(array_format="comma").stringify
+ else:
+ serialise = partial(stringify, array_format="comma")
+
+ assert unquote(serialise({"in": ["foo", "bar"]})) == "in=foo,bar"
+ assert unquote(serialise({"a": {"b": [True, False]}})) == "a[b]=true,false"
+ assert unquote(serialise({"a": {"b": [True, False, None, True]}})) == "a[b]=true,false,true"
+
+
+def test_array_repeat() -> None:
+ assert unquote(stringify({"in": ["foo", "bar"]})) == "in=foo&in=bar"
+ assert unquote(stringify({"a": {"b": [True, False]}})) == "a[b]=true&a[b]=false"
+ assert unquote(stringify({"a": {"b": [True, False, None, True]}})) == "a[b]=true&a[b]=false&a[b]=true"
+ assert unquote(stringify({"in": ["foo", {"b": {"c": ["d", "e"]}}]})) == "in=foo&in[b][c]=d&in[b][c]=e"
+
+
+@pytest.mark.parametrize("method", ["class", "function"])
+def test_array_brackets(method: str) -> None:
+ if method == "class":
+ serialise = Querystring(array_format="brackets").stringify
+ else:
+ serialise = partial(stringify, array_format="brackets")
+
+ assert unquote(serialise({"in": ["foo", "bar"]})) == "in[]=foo&in[]=bar"
+ assert unquote(serialise({"a": {"b": [True, False]}})) == "a[b][]=true&a[b][]=false"
+ assert unquote(serialise({"a": {"b": [True, False, None, True]}})) == "a[b][]=true&a[b][]=false&a[b][]=true"
+
+
+def test_unknown_array_format() -> None:
+ with pytest.raises(NotImplementedError, match="Unknown array_format value: foo, choose from comma, repeat"):
+ stringify({"a": ["foo", "bar"]}, array_format=cast(Any, "foo"))
diff --git a/tests/test_required_args.py b/tests/test_required_args.py
new file mode 100644
index 0000000..c6a4010
--- /dev/null
+++ b/tests/test_required_args.py
@@ -0,0 +1,111 @@
+from __future__ import annotations
+
+import pytest
+
+from opencode._utils import required_args
+
+
+def test_too_many_positional_params() -> None:
+ @required_args(["a"])
+ def foo(a: str | None = None) -> str | None:
+ return a
+
+ with pytest.raises(TypeError, match=r"foo\(\) takes 1 argument\(s\) but 2 were given"):
+ foo("a", "b") # type: ignore
+
+
+def test_positional_param() -> None:
+ @required_args(["a"])
+ def foo(a: str | None = None) -> str | None:
+ return a
+
+ assert foo("a") == "a"
+ assert foo(None) is None
+ assert foo(a="b") == "b"
+
+ with pytest.raises(TypeError, match="Missing required argument: 'a'"):
+ foo()
+
+
+def test_keyword_only_param() -> None:
+ @required_args(["a"])
+ def foo(*, a: str | None = None) -> str | None:
+ return a
+
+ assert foo(a="a") == "a"
+ assert foo(a=None) is None
+ assert foo(a="b") == "b"
+
+ with pytest.raises(TypeError, match="Missing required argument: 'a'"):
+ foo()
+
+
+def test_multiple_params() -> None:
+ @required_args(["a", "b", "c"])
+ def foo(a: str = "", *, b: str = "", c: str = "") -> str | None:
+ return f"{a} {b} {c}"
+
+ assert foo(a="a", b="b", c="c") == "a b c"
+
+ error_message = r"Missing required arguments.*"
+
+ with pytest.raises(TypeError, match=error_message):
+ foo()
+
+ with pytest.raises(TypeError, match=error_message):
+ foo(a="a")
+
+ with pytest.raises(TypeError, match=error_message):
+ foo(b="b")
+
+ with pytest.raises(TypeError, match=error_message):
+ foo(c="c")
+
+ with pytest.raises(TypeError, match=r"Missing required argument: 'a'"):
+ foo(b="a", c="c")
+
+ with pytest.raises(TypeError, match=r"Missing required argument: 'b'"):
+ foo("a", c="c")
+
+
+def test_multiple_variants() -> None:
+ @required_args(["a"], ["b"])
+ def foo(*, a: str | None = None, b: str | None = None) -> str | None:
+ return a if a is not None else b
+
+ assert foo(a="foo") == "foo"
+ assert foo(b="bar") == "bar"
+ assert foo(a=None) is None
+ assert foo(b=None) is None
+
+ # TODO: this error message could probably be improved
+ with pytest.raises(
+ TypeError,
+ match=r"Missing required arguments; Expected either \('a'\) or \('b'\) arguments to be given",
+ ):
+ foo()
+
+
+def test_multiple_params_multiple_variants() -> None:
+ @required_args(["a", "b"], ["c"])
+ def foo(*, a: str | None = None, b: str | None = None, c: str | None = None) -> str | None:
+ if a is not None:
+ return a
+ if b is not None:
+ return b
+ return c
+
+ error_message = r"Missing required arguments; Expected either \('a' and 'b'\) or \('c'\) arguments to be given"
+
+ with pytest.raises(TypeError, match=error_message):
+ foo(a="foo")
+
+ with pytest.raises(TypeError, match=error_message):
+ foo(b="bar")
+
+ with pytest.raises(TypeError, match=error_message):
+ foo()
+
+ assert foo(a=None, b="bar") == "bar"
+ assert foo(c=None) is None
+ assert foo(c="foo") == "foo"
diff --git a/tests/test_response.py b/tests/test_response.py
index 0cad3ee..130c423 100644
--- a/tests/test_response.py
+++ b/tests/test_response.py
@@ -1,155 +1,277 @@
-from __future__ import annotations
+import json
+from typing import Any, List, Union, cast
+from typing_extensions import Annotated
+import httpx
import pytest
-import respx
+import pydantic
-from opencode import (
+from opencode import Opencode, BaseModel, AsyncOpencode
+from opencode._response import (
APIResponse,
+ BaseAPIResponse,
AsyncAPIResponse,
- AsyncCursorPage,
- AsyncOpencode,
- NotFoundError,
- Opencode,
- Stream,
- SyncCursorPage,
+ BinaryAPIResponse,
+ AsyncBinaryAPIResponse,
+ extract_response_type,
)
-from opencode.types import ServerConnectedEvent, SessionInfo
+from opencode._streaming import Stream
+from opencode._base_client import FinalRequestOptions
-from .test_client import SESSION
-CONNECTED = '{"type":"server.connected","id":"evt_1","created":1,"data":{}}'
-SSE = {"content-type": "text/event-stream"}
+class ConcreteBaseAPIResponse(APIResponse[bytes]): ...
-def _page(ids: list[str], next_cursor: str | None) -> dict[str, object]:
- return {
- "data": [{"type": "user", "id": item, "time": {"created": 1}, "text": item} for item in ids],
- "cursor": {"previous": None, "next": next_cursor},
- }
+class ConcreteAPIResponse(APIResponse[List[str]]): ...
-def test_raw_response(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/session/ses_1").respond(json={"data": SESSION}, headers={"x-request-id": "req_1"})
- response = client.session.with_raw_response.get("ses_1")
- assert isinstance(response, APIResponse)
- assert response.status_code == 200
- assert response.headers["x-request-id"] == "req_1"
- assert response.request.method == "GET"
- assert response.http_response.is_success
- assert response.json() == {"data": SESSION}
- assert '"ses_1"' in response.text
- session = response.parse()
- assert isinstance(session, SessionInfo)
- assert session.id == "ses_1"
- assert response.parse() is session
+class ConcreteAsyncAPIResponse(APIResponse[httpx.Response]): ...
-def test_raw_response_void_binary_and_nested(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.delete("/api/session/ses_1/revert").respond(204)
- mock_api.get("/api/fs/read/a.txt").respond(content=b"hello")
- assert client.session.revert.with_raw_response.clear("ses_1").parse() is None
- assert client.file.with_raw_response.read("a.txt").parse() == b"hello"
+def test_extract_response_type_direct_classes() -> None:
+ assert extract_response_type(BaseAPIResponse[str]) == str
+ assert extract_response_type(APIResponse[str]) == str
+ assert extract_response_type(AsyncAPIResponse[str]) == str
-def test_raw_response_still_raises_status_errors(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/session/ses_9").respond(404, json={"message": "nope"})
- with pytest.raises(NotFoundError):
- client.session.with_raw_response.get("ses_9")
+def test_extract_response_type_direct_class_missing_type_arg() -> None:
+ with pytest.raises(
+ RuntimeError,
+ match="Expected type to have a type argument at index 0 but it did not",
+ ):
+ extract_response_type(AsyncAPIResponse)
-def test_raw_mode_does_not_leak(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/session/ses_1").respond(json={"data": SESSION})
- client.session.with_raw_response.get("ses_1")
- assert isinstance(client.session.get("ses_1"), SessionInfo)
+def test_extract_response_type_concrete_subclasses() -> None:
+ assert extract_response_type(ConcreteBaseAPIResponse) == bytes
+ assert extract_response_type(ConcreteAPIResponse) == List[str]
+ assert extract_response_type(ConcreteAsyncAPIResponse) == httpx.Response
-def test_raw_page_parses_to_a_working_page(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/session/ses_1/message").mock(
- side_effect=[respx.MockResponse(json=_page(["a"], "c1")), respx.MockResponse(json=_page(["b"], None))]
+def test_extract_response_type_binary_response() -> None:
+ assert extract_response_type(BinaryAPIResponse) == bytes
+ assert extract_response_type(AsyncBinaryAPIResponse) == bytes
+
+
+class PydanticModel(pydantic.BaseModel): ...
+
+
+def test_response_parse_mismatched_basemodel(client: Opencode) -> None:
+ response = APIResponse(
+ raw=httpx.Response(200, content=b"foo"),
+ client=client,
+ stream=False,
+ stream_cls=None,
+ cast_to=str,
+ options=FinalRequestOptions.construct(method="get", url="/foo"),
+ )
+
+ with pytest.raises(
+ TypeError,
+ match="Pydantic models must subclass our base model type, e.g. `from opencode import BaseModel`",
+ ):
+ response.parse(to=PydanticModel)
+
+
+@pytest.mark.asyncio
+async def test_async_response_parse_mismatched_basemodel(async_client: AsyncOpencode) -> None:
+ response = AsyncAPIResponse(
+ raw=httpx.Response(200, content=b"foo"),
+ client=async_client,
+ stream=False,
+ stream_cls=None,
+ cast_to=str,
+ options=FinalRequestOptions.construct(method="get", url="/foo"),
+ )
+
+ with pytest.raises(
+ TypeError,
+ match="Pydantic models must subclass our base model type, e.g. `from opencode import BaseModel`",
+ ):
+ await response.parse(to=PydanticModel)
+
+
+def test_response_parse_custom_stream(client: Opencode) -> None:
+ response = APIResponse(
+ raw=httpx.Response(200, content=b"foo"),
+ client=client,
+ stream=True,
+ stream_cls=None,
+ cast_to=str,
+ options=FinalRequestOptions.construct(method="get", url="/foo"),
+ )
+
+ stream = response.parse(to=Stream[int])
+ assert stream._cast_to == int
+
+
+@pytest.mark.asyncio
+async def test_async_response_parse_custom_stream(async_client: AsyncOpencode) -> None:
+ response = AsyncAPIResponse(
+ raw=httpx.Response(200, content=b"foo"),
+ client=async_client,
+ stream=True,
+ stream_cls=None,
+ cast_to=str,
+ options=FinalRequestOptions.construct(method="get", url="/foo"),
+ )
+
+ stream = await response.parse(to=Stream[int])
+ assert stream._cast_to == int
+
+
+class CustomModel(BaseModel):
+ foo: str
+ bar: int
+
+
+def test_response_parse_custom_model(client: Opencode) -> None:
+ response = APIResponse(
+ raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
+ client=client,
+ stream=False,
+ stream_cls=None,
+ cast_to=str,
+ options=FinalRequestOptions.construct(method="get", url="/foo"),
)
- response = client.message.with_raw_response.list("ses_1")
- page = response.parse()
- assert isinstance(page, SyncCursorPage)
- assert [message.id for message in page] == ["a", "b"]
+ obj = response.parse(to=CustomModel)
+ assert obj.foo == "hello!"
+ assert obj.bar == 2
-def test_streaming_response(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/fs/read/a.txt").respond(content=b"line 1\nline 2\n")
- with client.file.with_streaming_response.read("a.txt") as response:
- assert not response.http_response.is_stream_consumed
- assert list(response.iter_lines()) == ["line 1", "line 2"]
- assert response.is_closed
+@pytest.mark.asyncio
+async def test_async_response_parse_custom_model(async_client: AsyncOpencode) -> None:
+ response = AsyncAPIResponse(
+ raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
+ client=async_client,
+ stream=False,
+ stream_cls=None,
+ cast_to=str,
+ options=FinalRequestOptions.construct(method="get", url="/foo"),
+ )
+
+ obj = await response.parse(to=CustomModel)
+ assert obj.foo == "hello!"
+ assert obj.bar == 2
+
+
+def test_response_parse_annotated_type(client: Opencode) -> None:
+ response = APIResponse(
+ raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
+ client=client,
+ stream=False,
+ stream_cls=None,
+ cast_to=str,
+ options=FinalRequestOptions.construct(method="get", url="/foo"),
+ )
+
+ obj = response.parse(
+ to=cast("type[CustomModel]", Annotated[CustomModel, "random metadata"]),
+ )
+ assert obj.foo == "hello!"
+ assert obj.bar == 2
+
+
+async def test_async_response_parse_annotated_type(async_client: AsyncOpencode) -> None:
+ response = AsyncAPIResponse(
+ raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})),
+ client=async_client,
+ stream=False,
+ stream_cls=None,
+ cast_to=str,
+ options=FinalRequestOptions.construct(method="get", url="/foo"),
+ )
+
+ obj = await response.parse(
+ to=cast("type[CustomModel]", Annotated[CustomModel, "random metadata"]),
+ )
+ assert obj.foo == "hello!"
+ assert obj.bar == 2
+
+
+@pytest.mark.parametrize(
+ "content, expected",
+ [
+ ("false", False),
+ ("true", True),
+ ("False", False),
+ ("True", True),
+ ("TrUe", True),
+ ("FalSe", False),
+ ],
+)
+def test_response_parse_bool(client: Opencode, content: str, expected: bool) -> None:
+ response = APIResponse(
+ raw=httpx.Response(200, content=content),
+ client=client,
+ stream=False,
+ stream_cls=None,
+ cast_to=str,
+ options=FinalRequestOptions.construct(method="get", url="/foo"),
+ )
-def test_streaming_response_parse(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/session/ses_1").respond(json={"data": SESSION})
- with client.session.with_streaming_response.get("ses_1") as response:
- assert response.parse().id == "ses_1"
+ result = response.parse(to=bool)
+ assert result is expected
-def test_raw_event_stream(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/event").respond(headers=SSE, content=f"data: {CONNECTED}\n\n".encode())
- response = client.event.with_raw_response.subscribe()
- assert response.headers["content-type"] == "text/event-stream"
- with response.parse() as events:
- assert isinstance(events, Stream)
- assert isinstance(next(iter(events)), ServerConnectedEvent)
+@pytest.mark.parametrize(
+ "content, expected",
+ [
+ ("false", False),
+ ("true", True),
+ ("False", False),
+ ("True", True),
+ ("TrUe", True),
+ ("FalSe", False),
+ ],
+)
+async def test_async_response_parse_bool(client: AsyncOpencode, content: str, expected: bool) -> None:
+ response = AsyncAPIResponse(
+ raw=httpx.Response(200, content=content),
+ client=client,
+ stream=False,
+ stream_cls=None,
+ cast_to=str,
+ options=FinalRequestOptions.construct(method="get", url="/foo"),
+ )
+ result = await response.parse(to=bool)
+ assert result is expected
-@pytest.mark.anyio
-async def test_async_raw_response(async_client: AsyncOpencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/session/ses_1").respond(json={"data": SESSION}, headers={"x-request-id": "req_1"})
- response = await async_client.session.with_raw_response.get("ses_1")
- assert isinstance(response, AsyncAPIResponse)
- assert response.headers["x-request-id"] == "req_1"
- assert (await response.json()) == {"data": SESSION}
- assert (await response.parse()).id == "ses_1"
+class OtherModel(BaseModel):
+ a: str
-@pytest.mark.anyio
-async def test_async_raw_page(async_client: AsyncOpencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/session/ses_1/message").mock(
- side_effect=[respx.MockResponse(json=_page(["a"], "c1")), respx.MockResponse(json=_page(["b"], None))]
+
+@pytest.mark.parametrize("client", [False], indirect=True) # loose validation
+def test_response_parse_expect_model_union_non_json_content(client: Opencode) -> None:
+ response = APIResponse(
+ raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}),
+ client=client,
+ stream=False,
+ stream_cls=None,
+ cast_to=str,
+ options=FinalRequestOptions.construct(method="get", url="/foo"),
+ )
+
+ obj = response.parse(to=cast(Any, Union[CustomModel, OtherModel]))
+ assert isinstance(obj, str)
+ assert obj == "foo"
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("async_client", [False], indirect=True) # loose validation
+async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncOpencode) -> None:
+ response = AsyncAPIResponse(
+ raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}),
+ client=async_client,
+ stream=False,
+ stream_cls=None,
+ cast_to=str,
+ options=FinalRequestOptions.construct(method="get", url="/foo"),
)
- response = await async_client.message.with_raw_response.list("ses_1")
- page = await response.parse()
- assert isinstance(page, AsyncCursorPage)
- assert [message.id async for message in page] == ["a", "b"]
-
-
-@pytest.mark.anyio
-async def test_async_streaming_response(async_client: AsyncOpencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/fs/read/a.txt").respond(content=b"hello")
- async with async_client.file.with_streaming_response.read("a.txt") as response:
- assert b"".join([chunk async for chunk in response.iter_bytes()]) == b"hello"
- assert response.is_closed
- mock_api.get("/api/session/ses_1").respond(json={"data": SESSION})
- async with async_client.session.with_streaming_response.get("ses_1") as response:
- assert (await response.parse()).id == "ses_1"
-
-
-@pytest.mark.anyio
-async def test_async_raw_event_stream(async_client: AsyncOpencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/event").respond(headers=SSE, content=f"data: {CONNECTED}\n\n".encode())
- response = await async_client.event.with_raw_response.subscribe()
- async with await response.parse() as events:
- async for event in events:
- assert isinstance(event, ServerConnectedEvent)
- break
-
-
-def test_custom_requests(client: Opencode, mock_api: respx.MockRouter) -> None:
- route = mock_api.post("/api/custom").respond(json={"ok": True})
- assert client.post("/api/custom", body={"a": 1}, query={"q": "x"}, headers={"X-A": "b"}) == {"ok": True}
- request = route.calls.last.request
- assert request.content == b'{"a":1}'
- assert request.url.params["q"] == "x"
- assert request.headers["X-A"] == "b"
- mock_api.get("/api/session/ses_1").respond(json={"data": SESSION})
- assert client.get("/api/session/ses_1", cast_to=dict[str, SessionInfo])["data"].id == "ses_1"
-
-
-@pytest.mark.anyio
-async def test_async_custom_requests(async_client: AsyncOpencode, mock_api: respx.MockRouter) -> None:
- mock_api.delete("/api/custom").respond(json=[1])
- assert await async_client.delete("/api/custom", cast_to=list[int]) == [1]
+
+ obj = await response.parse(to=cast(Any, Union[CustomModel, OtherModel]))
+ assert isinstance(obj, str)
+ assert obj == "foo"
diff --git a/tests/test_streaming.py b/tests/test_streaming.py
index 4721e44..ad34b32 100644
--- a/tests/test_streaming.py
+++ b/tests/test_streaming.py
@@ -1,78 +1,248 @@
from __future__ import annotations
+from typing import Iterator, AsyncIterator
+
import httpx
import pytest
-import respx
-from opencode import APIResponseValidationError, AsyncOpencode, Opencode
-from opencode._streaming import _Decoder, _LineSplitter
-from opencode.types import ServerConnectedEvent, UnknownEvent
+from opencode import Opencode, AsyncOpencode
+from opencode._streaming import Stream, AsyncStream, ServerSentEvent
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_basic(sync: bool, client: Opencode, async_client: AsyncOpencode) -> None:
+ def body() -> Iterator[bytes]:
+ yield b"event: completion\n"
+ yield b'data: {"foo":true}\n'
+ yield b"\n"
+
+ iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+ sse = await iter_next(iterator)
+ assert sse.event == "completion"
+ assert sse.json() == {"foo": True}
+
+ await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_data_missing_event(sync: bool, client: Opencode, async_client: AsyncOpencode) -> None:
+ def body() -> Iterator[bytes]:
+ yield b'data: {"foo":true}\n'
+ yield b"\n"
+
+ iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+ sse = await iter_next(iterator)
+ assert sse.event is None
+ assert sse.json() == {"foo": True}
+
+ await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_event_missing_data(sync: bool, client: Opencode, async_client: AsyncOpencode) -> None:
+ def body() -> Iterator[bytes]:
+ yield b"event: ping\n"
+ yield b"\n"
+
+ iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+ sse = await iter_next(iterator)
+ assert sse.event == "ping"
+ assert sse.data == ""
+
+ await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_multiple_events(sync: bool, client: Opencode, async_client: AsyncOpencode) -> None:
+ def body() -> Iterator[bytes]:
+ yield b"event: ping\n"
+ yield b"\n"
+ yield b"event: completion\n"
+ yield b"\n"
+
+ iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+ sse = await iter_next(iterator)
+ assert sse.event == "ping"
+ assert sse.data == ""
+
+ sse = await iter_next(iterator)
+ assert sse.event == "completion"
+ assert sse.data == ""
+
+ await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_multiple_events_with_data(sync: bool, client: Opencode, async_client: AsyncOpencode) -> None:
+ def body() -> Iterator[bytes]:
+ yield b"event: ping\n"
+ yield b'data: {"foo":true}\n'
+ yield b"\n"
+ yield b"event: completion\n"
+ yield b'data: {"bar":false}\n'
+ yield b"\n"
+
+ iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+ sse = await iter_next(iterator)
+ assert sse.event == "ping"
+ assert sse.json() == {"foo": True}
+
+ sse = await iter_next(iterator)
+ assert sse.event == "completion"
+ assert sse.json() == {"bar": False}
+
+ await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_multiple_data_lines_with_empty_line(sync: bool, client: Opencode, async_client: AsyncOpencode) -> None:
+ def body() -> Iterator[bytes]:
+ yield b"event: ping\n"
+ yield b"data: {\n"
+ yield b'data: "foo":\n'
+ yield b"data: \n"
+ yield b"data:\n"
+ yield b"data: true}\n"
+ yield b"\n\n"
+
+ iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+ sse = await iter_next(iterator)
+ assert sse.event == "ping"
+ assert sse.json() == {"foo": True}
+ assert sse.data == '{\n"foo":\n\n\ntrue}'
+
+ await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_data_json_escaped_double_new_line(sync: bool, client: Opencode, async_client: AsyncOpencode) -> None:
+ def body() -> Iterator[bytes]:
+ yield b"event: ping\n"
+ yield b'data: {"foo": "my long\\n\\ncontent"}'
+ yield b"\n\n"
+
+ iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
+
+ sse = await iter_next(iterator)
+ assert sse.event == "ping"
+ assert sse.json() == {"foo": "my long\n\ncontent"}
+
+ await assert_empty_iter(iterator)
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_multiple_data_lines(sync: bool, client: Opencode, async_client: AsyncOpencode) -> None:
+ def body() -> Iterator[bytes]:
+ yield b"event: ping\n"
+ yield b"data: {\n"
+ yield b'data: "foo":\n'
+ yield b"data: true}\n"
+ yield b"\n\n"
+
+ iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
-CONNECTED = '{"type":"server.connected","id":"evt_1","created":1,"data":{}}'
+ sse = await iter_next(iterator)
+ assert sse.event == "ping"
+ assert sse.json() == {"foo": True}
+
+ await assert_empty_iter(iterator)
-def _sse(*blocks: str) -> bytes:
- return "".join(blocks).encode()
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_special_new_line_character(
+ sync: bool,
+ client: Opencode,
+ async_client: AsyncOpencode,
+) -> None:
+ def body() -> Iterator[bytes]:
+ yield b'data: {"content":" culpa"}\n'
+ yield b"\n"
+ yield b'data: {"content":" \xe2\x80\xa8"}\n'
+ yield b"\n"
+ yield b'data: {"content":"foo"}\n'
+ yield b"\n"
+ iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
-def test_line_splitter_handles_every_newline_style_across_chunks() -> None:
- splitter = _LineSplitter()
- lines = [*splitter.feed("a\r"), *splitter.feed("\nb\rc\nd"), *splitter.feed("e\r\n"), *splitter.flush()]
- assert lines == ["a", "b", "c", "de", ""]
+ sse = await iter_next(iterator)
+ assert sse.event is None
+ assert sse.json() == {"content": " culpa"}
+
+ sse = await iter_next(iterator)
+ assert sse.event is None
+ assert sse.json() == {"content": "
"}
+ sse = await iter_next(iterator)
+ assert sse.event is None
+ assert sse.json() == {"content": "foo"}
-def test_decoder_joins_multiline_data_and_ignores_comments() -> None:
- decoder = _Decoder()
- events = [decoder.feed_line(line) for line in [": keepalive", "event: x", "data: one", "data:two", "id: 7", ""]]
- event = events[-1]
- assert event is not None
- assert (event.data, event.event, event.id) == ("one\ntwo", "x", "7")
- assert all(item is None for item in events[:-1])
+ await assert_empty_iter(iterator)
-def test_sync_stream_yields_typed_events(client: Opencode, mock_api: respx.MockRouter) -> None:
- body = _sse(": hi\n\n", f"data: {CONNECTED}\n\n", 'data: {"type":"brand.new","id":"e","created":1}\n\n')
- route = mock_api.get("/api/event").respond(headers={"content-type": "text/event-stream"}, content=body)
- with client.event.subscribe() as events:
- received = list(events)
- assert isinstance(received[0], ServerConnectedEvent)
- assert isinstance(received[1], UnknownEvent)
- assert route.calls.last.request.headers["Accept"] == "text/event-stream"
+@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
+async def test_multi_byte_character_multiple_chunks(
+ sync: bool,
+ client: Opencode,
+ async_client: AsyncOpencode,
+) -> None:
+ def body() -> Iterator[bytes]:
+ yield b'data: {"content":"'
+ # bytes taken from the string 'известни' and arbitrarily split
+ # so that some multi-byte characters span multiple chunks
+ yield b"\xd0"
+ yield b"\xb8\xd0\xb7\xd0"
+ yield b"\xb2\xd0\xb5\xd1\x81\xd1\x82\xd0\xbd\xd0\xb8"
+ yield b'"}\n'
+ yield b"\n"
+ iterator = make_event_iterator(content=body(), sync=sync, client=client, async_client=async_client)
-def test_stream_rejects_wrong_content_type(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/event").respond(json={"not": "a stream"})
- with pytest.raises(APIResponseValidationError, match="event stream"):
- client.event.subscribe()
+ sse = await iter_next(iterator)
+ assert sse.event is None
+ assert sse.json() == {"content": "известни"}
-def test_stream_reports_malformed_event(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/event").respond(headers={"content-type": "text/event-stream"}, content=b"data: {oops\n\n")
- with pytest.raises(APIResponseValidationError, match="Malformed"), client.event.subscribe() as events:
- list(events)
+async def to_aiter(iter: Iterator[bytes]) -> AsyncIterator[bytes]:
+ for chunk in iter:
+ yield chunk
-def test_stream_has_no_read_timeout_by_default(client: Opencode, mock_api: respx.MockRouter) -> None:
- route = mock_api.get("/api/event").respond(headers={"content-type": "text/event-stream"}, content=b"")
- client.event.subscribe().close()
- timeout = route.calls.last.request.extensions["timeout"]
- assert timeout["read"] is None
- assert timeout["connect"] == 10.0
+async def iter_next(iter: Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]) -> ServerSentEvent:
+ if isinstance(iter, AsyncIterator):
+ return await iter.__anext__()
+
+ return next(iter)
-@pytest.mark.anyio
-async def test_async_stream(async_client: AsyncOpencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/event").respond(
- headers={"content-type": "text/event-stream"}, content=_sse(f"data: {CONNECTED}\r\n\r\n")
- )
- async with await async_client.event.subscribe() as events:
- received = [event async for event in events]
- assert [event.type for event in received] == ["server.connected"]
+async def assert_empty_iter(iter: Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]) -> None:
+ with pytest.raises((StopAsyncIteration, RuntimeError)):
+ await iter_next(iter)
-def test_stream_error_status_raises(client: Opencode, mock_api: respx.MockRouter) -> None:
- mock_api.get("/api/event").mock(
- return_value=httpx.Response(401, json={"_tag": "UnauthorizedError", "message": "no"})
- )
- with pytest.raises(Exception, match="no"):
- client.event.subscribe()
+def make_event_iterator(
+ content: Iterator[bytes],
+ *,
+ sync: bool,
+ client: Opencode,
+ async_client: AsyncOpencode,
+) -> Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]:
+ if sync:
+ return Stream(cast_to=object, client=client, response=httpx.Response(200, content=content))._iter_events()
+
+ return AsyncStream(
+ cast_to=object, client=async_client, response=httpx.Response(200, content=to_aiter(content))
+ )._iter_events()
diff --git a/tests/test_transform.py b/tests/test_transform.py
new file mode 100644
index 0000000..2f7142c
--- /dev/null
+++ b/tests/test_transform.py
@@ -0,0 +1,460 @@
+from __future__ import annotations
+
+import io
+import pathlib
+from typing import Any, Dict, List, Union, TypeVar, Iterable, Optional, cast
+from datetime import date, datetime
+from typing_extensions import Required, Annotated, TypedDict
+
+import pytest
+
+from opencode._types import Base64FileInput, omit, not_given
+from opencode._utils import (
+ PropertyInfo,
+ transform as _transform,
+ parse_datetime,
+ async_transform as _async_transform,
+)
+from opencode._compat import PYDANTIC_V1
+from opencode._models import BaseModel
+
+_T = TypeVar("_T")
+
+SAMPLE_FILE_PATH = pathlib.Path(__file__).parent.joinpath("sample_file.txt")
+
+
+async def transform(
+ data: _T,
+ expected_type: object,
+ use_async: bool,
+) -> _T:
+ if use_async:
+ return await _async_transform(data, expected_type=expected_type)
+
+ return _transform(data, expected_type=expected_type)
+
+
+parametrize = pytest.mark.parametrize("use_async", [False, True], ids=["sync", "async"])
+
+
+class Foo1(TypedDict):
+ foo_bar: Annotated[str, PropertyInfo(alias="fooBar")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_top_level_alias(use_async: bool) -> None:
+ assert await transform({"foo_bar": "hello"}, expected_type=Foo1, use_async=use_async) == {"fooBar": "hello"}
+
+
+class Foo2(TypedDict):
+ bar: Bar2
+
+
+class Bar2(TypedDict):
+ this_thing: Annotated[int, PropertyInfo(alias="this__thing")]
+ baz: Annotated[Baz2, PropertyInfo(alias="Baz")]
+
+
+class Baz2(TypedDict):
+ my_baz: Annotated[str, PropertyInfo(alias="myBaz")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_recursive_typeddict(use_async: bool) -> None:
+ assert await transform({"bar": {"this_thing": 1}}, Foo2, use_async) == {"bar": {"this__thing": 1}}
+ assert await transform({"bar": {"baz": {"my_baz": "foo"}}}, Foo2, use_async) == {"bar": {"Baz": {"myBaz": "foo"}}}
+
+
+class Foo3(TypedDict):
+ things: List[Bar3]
+
+
+class Bar3(TypedDict):
+ my_field: Annotated[str, PropertyInfo(alias="myField")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_list_of_typeddict(use_async: bool) -> None:
+ result = await transform({"things": [{"my_field": "foo"}, {"my_field": "foo2"}]}, Foo3, use_async)
+ assert result == {"things": [{"myField": "foo"}, {"myField": "foo2"}]}
+
+
+class Foo4(TypedDict):
+ foo: Union[Bar4, Baz4]
+
+
+class Bar4(TypedDict):
+ foo_bar: Annotated[str, PropertyInfo(alias="fooBar")]
+
+
+class Baz4(TypedDict):
+ foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_union_of_typeddict(use_async: bool) -> None:
+ assert await transform({"foo": {"foo_bar": "bar"}}, Foo4, use_async) == {"foo": {"fooBar": "bar"}}
+ assert await transform({"foo": {"foo_baz": "baz"}}, Foo4, use_async) == {"foo": {"fooBaz": "baz"}}
+ assert await transform({"foo": {"foo_baz": "baz", "foo_bar": "bar"}}, Foo4, use_async) == {
+ "foo": {"fooBaz": "baz", "fooBar": "bar"}
+ }
+
+
+class Foo5(TypedDict):
+ foo: Annotated[Union[Bar4, List[Baz4]], PropertyInfo(alias="FOO")]
+
+
+class Bar5(TypedDict):
+ foo_bar: Annotated[str, PropertyInfo(alias="fooBar")]
+
+
+class Baz5(TypedDict):
+ foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_union_of_list(use_async: bool) -> None:
+ assert await transform({"foo": {"foo_bar": "bar"}}, Foo5, use_async) == {"FOO": {"fooBar": "bar"}}
+ assert await transform(
+ {
+ "foo": [
+ {"foo_baz": "baz"},
+ {"foo_baz": "baz"},
+ ]
+ },
+ Foo5,
+ use_async,
+ ) == {"FOO": [{"fooBaz": "baz"}, {"fooBaz": "baz"}]}
+
+
+class Foo6(TypedDict):
+ bar: Annotated[str, PropertyInfo(alias="Bar")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_includes_unknown_keys(use_async: bool) -> None:
+ assert await transform({"bar": "bar", "baz_": {"FOO": 1}}, Foo6, use_async) == {
+ "Bar": "bar",
+ "baz_": {"FOO": 1},
+ }
+
+
+class Foo7(TypedDict):
+ bar: Annotated[List[Bar7], PropertyInfo(alias="bAr")]
+ foo: Bar7
+
+
+class Bar7(TypedDict):
+ foo: str
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_ignores_invalid_input(use_async: bool) -> None:
+ assert await transform({"bar": ""}, Foo7, use_async) == {"bAr": ""}
+ assert await transform({"foo": ""}, Foo7, use_async) == {"foo": ""}
+
+
+class DatetimeDict(TypedDict, total=False):
+ foo: Annotated[datetime, PropertyInfo(format="iso8601")]
+
+ bar: Annotated[Optional[datetime], PropertyInfo(format="iso8601")]
+
+ required: Required[Annotated[Optional[datetime], PropertyInfo(format="iso8601")]]
+
+ list_: Required[Annotated[Optional[List[datetime]], PropertyInfo(format="iso8601")]]
+
+ union: Annotated[Union[int, datetime], PropertyInfo(format="iso8601")]
+
+
+class DateDict(TypedDict, total=False):
+ foo: Annotated[date, PropertyInfo(format="iso8601")]
+
+
+class DatetimeModel(BaseModel):
+ foo: datetime
+
+
+class DateModel(BaseModel):
+ foo: Optional[date]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_iso8601_format(use_async: bool) -> None:
+ dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00")
+ tz = "+00:00" if PYDANTIC_V1 else "Z"
+ assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap]
+ assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692" + tz} # type: ignore[comparison-overlap]
+
+ dt = dt.replace(tzinfo=None)
+ assert await transform({"foo": dt}, DatetimeDict, use_async) == {"foo": "2023-02-23T14:16:36.337692"} # type: ignore[comparison-overlap]
+ assert await transform(DatetimeModel(foo=dt), Any, use_async) == {"foo": "2023-02-23T14:16:36.337692"} # type: ignore[comparison-overlap]
+
+ assert await transform({"foo": None}, DateDict, use_async) == {"foo": None} # type: ignore[comparison-overlap]
+ assert await transform(DateModel(foo=None), Any, use_async) == {"foo": None} # type: ignore
+ assert await transform({"foo": date.fromisoformat("2023-02-23")}, DateDict, use_async) == {"foo": "2023-02-23"} # type: ignore[comparison-overlap]
+ assert await transform(DateModel(foo=date.fromisoformat("2023-02-23")), DateDict, use_async) == {
+ "foo": "2023-02-23"
+ } # type: ignore[comparison-overlap]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_optional_iso8601_format(use_async: bool) -> None:
+ dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00")
+ assert await transform({"bar": dt}, DatetimeDict, use_async) == {"bar": "2023-02-23T14:16:36.337692+00:00"} # type: ignore[comparison-overlap]
+
+ assert await transform({"bar": None}, DatetimeDict, use_async) == {"bar": None}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_required_iso8601_format(use_async: bool) -> None:
+ dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00")
+ assert await transform({"required": dt}, DatetimeDict, use_async) == {
+ "required": "2023-02-23T14:16:36.337692+00:00"
+ } # type: ignore[comparison-overlap]
+
+ assert await transform({"required": None}, DatetimeDict, use_async) == {"required": None}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_union_datetime(use_async: bool) -> None:
+ dt = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00")
+ assert await transform({"union": dt}, DatetimeDict, use_async) == { # type: ignore[comparison-overlap]
+ "union": "2023-02-23T14:16:36.337692+00:00"
+ }
+
+ assert await transform({"union": "foo"}, DatetimeDict, use_async) == {"union": "foo"}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_nested_list_iso6801_format(use_async: bool) -> None:
+ dt1 = datetime.fromisoformat("2023-02-23T14:16:36.337692+00:00")
+ dt2 = parse_datetime("2022-01-15T06:34:23Z")
+ assert await transform({"list_": [dt1, dt2]}, DatetimeDict, use_async) == { # type: ignore[comparison-overlap]
+ "list_": ["2023-02-23T14:16:36.337692+00:00", "2022-01-15T06:34:23+00:00"]
+ }
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_datetime_custom_format(use_async: bool) -> None:
+ dt = parse_datetime("2022-01-15T06:34:23Z")
+
+ result = await transform(dt, Annotated[datetime, PropertyInfo(format="custom", format_template="%H")], use_async)
+ assert result == "06" # type: ignore[comparison-overlap]
+
+
+class DateDictWithRequiredAlias(TypedDict, total=False):
+ required_prop: Required[Annotated[date, PropertyInfo(format="iso8601", alias="prop")]]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_datetime_with_alias(use_async: bool) -> None:
+ assert await transform({"required_prop": None}, DateDictWithRequiredAlias, use_async) == {"prop": None} # type: ignore[comparison-overlap]
+ assert await transform(
+ {"required_prop": date.fromisoformat("2023-02-23")}, DateDictWithRequiredAlias, use_async
+ ) == {"prop": "2023-02-23"} # type: ignore[comparison-overlap]
+
+
+class MyModel(BaseModel):
+ foo: str
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_model_to_dictionary(use_async: bool) -> None:
+ assert cast(Any, await transform(MyModel(foo="hi!"), Any, use_async)) == {"foo": "hi!"}
+ assert cast(Any, await transform(MyModel.construct(foo="hi!"), Any, use_async)) == {"foo": "hi!"}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_empty_model(use_async: bool) -> None:
+ assert cast(Any, await transform(MyModel.construct(), Any, use_async)) == {}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_unknown_field(use_async: bool) -> None:
+ assert cast(Any, await transform(MyModel.construct(my_untyped_field=True), Any, use_async)) == {
+ "my_untyped_field": True
+ }
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_mismatched_types(use_async: bool) -> None:
+ model = MyModel.construct(foo=True)
+ if PYDANTIC_V1:
+ params = await transform(model, Any, use_async)
+ else:
+ with pytest.warns(UserWarning):
+ params = await transform(model, Any, use_async)
+ assert cast(Any, params) == {"foo": True}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_mismatched_object_type(use_async: bool) -> None:
+ model = MyModel.construct(foo=MyModel.construct(hello="world"))
+ if PYDANTIC_V1:
+ params = await transform(model, Any, use_async)
+ else:
+ with pytest.warns(UserWarning):
+ params = await transform(model, Any, use_async)
+ assert cast(Any, params) == {"foo": {"hello": "world"}}
+
+
+class ModelNestedObjects(BaseModel):
+ nested: MyModel
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_nested_objects(use_async: bool) -> None:
+ model = ModelNestedObjects.construct(nested={"foo": "stainless"})
+ assert isinstance(model.nested, MyModel)
+ assert cast(Any, await transform(model, Any, use_async)) == {"nested": {"foo": "stainless"}}
+
+
+class ModelWithDefaultField(BaseModel):
+ foo: str
+ with_none_default: Union[str, None] = None
+ with_str_default: str = "foo"
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_pydantic_default_field(use_async: bool) -> None:
+ # should be excluded when defaults are used
+ model = ModelWithDefaultField.construct()
+ assert model.with_none_default is None
+ assert model.with_str_default == "foo"
+ assert cast(Any, await transform(model, Any, use_async)) == {}
+
+ # should be included when the default value is explicitly given
+ model = ModelWithDefaultField.construct(with_none_default=None, with_str_default="foo")
+ assert model.with_none_default is None
+ assert model.with_str_default == "foo"
+ assert cast(Any, await transform(model, Any, use_async)) == {"with_none_default": None, "with_str_default": "foo"}
+
+ # should be included when a non-default value is explicitly given
+ model = ModelWithDefaultField.construct(with_none_default="bar", with_str_default="baz")
+ assert model.with_none_default == "bar"
+ assert model.with_str_default == "baz"
+ assert cast(Any, await transform(model, Any, use_async)) == {"with_none_default": "bar", "with_str_default": "baz"}
+
+
+class TypedDictIterableUnion(TypedDict):
+ foo: Annotated[Union[Bar8, Iterable[Baz8]], PropertyInfo(alias="FOO")]
+
+
+class Bar8(TypedDict):
+ foo_bar: Annotated[str, PropertyInfo(alias="fooBar")]
+
+
+class Baz8(TypedDict):
+ foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_iterable_of_dictionaries(use_async: bool) -> None:
+ assert await transform({"foo": [{"foo_baz": "bar"}]}, TypedDictIterableUnion, use_async) == {
+ "FOO": [{"fooBaz": "bar"}]
+ }
+ assert cast(Any, await transform({"foo": ({"foo_baz": "bar"},)}, TypedDictIterableUnion, use_async)) == {
+ "FOO": [{"fooBaz": "bar"}]
+ }
+
+ def my_iter() -> Iterable[Baz8]:
+ yield {"foo_baz": "hello"}
+ yield {"foo_baz": "world"}
+
+ assert await transform({"foo": my_iter()}, TypedDictIterableUnion, use_async) == {
+ "FOO": [{"fooBaz": "hello"}, {"fooBaz": "world"}]
+ }
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_dictionary_items(use_async: bool) -> None:
+ class DictItems(TypedDict):
+ foo_baz: Annotated[str, PropertyInfo(alias="fooBaz")]
+
+ assert await transform({"foo": {"foo_baz": "bar"}}, Dict[str, DictItems], use_async) == {"foo": {"fooBaz": "bar"}}
+
+
+class TypedDictIterableUnionStr(TypedDict):
+ foo: Annotated[Union[str, Iterable[Baz8]], PropertyInfo(alias="FOO")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_iterable_union_str(use_async: bool) -> None:
+ assert await transform({"foo": "bar"}, TypedDictIterableUnionStr, use_async) == {"FOO": "bar"}
+ assert cast(Any, await transform(iter([{"foo_baz": "bar"}]), Union[str, Iterable[Baz8]], use_async)) == [
+ {"fooBaz": "bar"}
+ ]
+
+
+class TypedDictBase64Input(TypedDict):
+ foo: Annotated[Union[str, Base64FileInput], PropertyInfo(format="base64")]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_base64_file_input(use_async: bool) -> None:
+ # strings are left as-is
+ assert await transform({"foo": "bar"}, TypedDictBase64Input, use_async) == {"foo": "bar"}
+
+ # pathlib.Path is automatically converted to base64
+ assert await transform({"foo": SAMPLE_FILE_PATH}, TypedDictBase64Input, use_async) == {
+ "foo": "SGVsbG8sIHdvcmxkIQo="
+ } # type: ignore[comparison-overlap]
+
+ # io instances are automatically converted to base64
+ assert await transform({"foo": io.StringIO("Hello, world!")}, TypedDictBase64Input, use_async) == {
+ "foo": "SGVsbG8sIHdvcmxkIQ=="
+ } # type: ignore[comparison-overlap]
+ assert await transform({"foo": io.BytesIO(b"Hello, world!")}, TypedDictBase64Input, use_async) == {
+ "foo": "SGVsbG8sIHdvcmxkIQ=="
+ } # type: ignore[comparison-overlap]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_transform_skipping(use_async: bool) -> None:
+ # lists of ints are left as-is
+ data = [1, 2, 3]
+ assert await transform(data, List[int], use_async) is data
+
+ # iterables of ints are converted to a list
+ data = iter([1, 2, 3])
+ assert await transform(data, Iterable[int], use_async) == [1, 2, 3]
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_strips_notgiven(use_async: bool) -> None:
+ assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"}
+ assert await transform({"foo_bar": not_given}, Foo1, use_async) == {}
+
+
+@parametrize
+@pytest.mark.asyncio
+async def test_strips_omit(use_async: bool) -> None:
+ assert await transform({"foo_bar": "bar"}, Foo1, use_async) == {"fooBar": "bar"}
+ assert await transform({"foo_bar": omit}, Foo1, use_async) == {}
diff --git a/tests/test_utils/test_datetime_parse.py b/tests/test_utils/test_datetime_parse.py
new file mode 100644
index 0000000..b0d7d60
--- /dev/null
+++ b/tests/test_utils/test_datetime_parse.py
@@ -0,0 +1,110 @@
+"""
+Copied from https://github.com/pydantic/pydantic/blob/v1.10.22/tests/test_datetime_parse.py
+with modifications so it works without pydantic v1 imports.
+"""
+
+from typing import Type, Union
+from datetime import date, datetime, timezone, timedelta
+
+import pytest
+
+from opencode._utils import parse_date, parse_datetime
+
+
+def create_tz(minutes: int) -> timezone:
+ return timezone(timedelta(minutes=minutes))
+
+
+@pytest.mark.parametrize(
+ "value,result",
+ [
+ # Valid inputs
+ ("1494012444.883309", date(2017, 5, 5)),
+ (b"1494012444.883309", date(2017, 5, 5)),
+ (1_494_012_444.883_309, date(2017, 5, 5)),
+ ("1494012444", date(2017, 5, 5)),
+ (1_494_012_444, date(2017, 5, 5)),
+ (0, date(1970, 1, 1)),
+ ("2012-04-23", date(2012, 4, 23)),
+ (b"2012-04-23", date(2012, 4, 23)),
+ ("2012-4-9", date(2012, 4, 9)),
+ (date(2012, 4, 9), date(2012, 4, 9)),
+ (datetime(2012, 4, 9, 12, 15), date(2012, 4, 9)),
+ # Invalid inputs
+ ("x20120423", ValueError),
+ ("2012-04-56", ValueError),
+ (19_999_999_999, date(2603, 10, 11)), # just before watershed
+ (20_000_000_001, date(1970, 8, 20)), # just after watershed
+ (1_549_316_052, date(2019, 2, 4)), # nowish in s
+ (1_549_316_052_104, date(2019, 2, 4)), # nowish in ms
+ (1_549_316_052_104_324, date(2019, 2, 4)), # nowish in μs
+ (1_549_316_052_104_324_096, date(2019, 2, 4)), # nowish in ns
+ ("infinity", date(9999, 12, 31)),
+ ("inf", date(9999, 12, 31)),
+ (float("inf"), date(9999, 12, 31)),
+ ("infinity ", date(9999, 12, 31)),
+ (int("1" + "0" * 100), date(9999, 12, 31)),
+ (1e1000, date(9999, 12, 31)),
+ ("-infinity", date(1, 1, 1)),
+ ("-inf", date(1, 1, 1)),
+ ("nan", ValueError),
+ ],
+)
+def test_date_parsing(value: Union[str, bytes, int, float], result: Union[date, Type[Exception]]) -> None:
+ if type(result) == type and issubclass(result, Exception): # pyright: ignore[reportUnnecessaryIsInstance]
+ with pytest.raises(result):
+ parse_date(value)
+ else:
+ assert parse_date(value) == result
+
+
+@pytest.mark.parametrize(
+ "value,result",
+ [
+ # Valid inputs
+ # values in seconds
+ ("1494012444.883309", datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)),
+ (1_494_012_444.883_309, datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)),
+ ("1494012444", datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)),
+ (b"1494012444", datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)),
+ (1_494_012_444, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)),
+ # values in ms
+ ("1494012444000.883309", datetime(2017, 5, 5, 19, 27, 24, 883, tzinfo=timezone.utc)),
+ ("-1494012444000.883309", datetime(1922, 8, 29, 4, 32, 35, 999117, tzinfo=timezone.utc)),
+ (1_494_012_444_000, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)),
+ ("2012-04-23T09:15:00", datetime(2012, 4, 23, 9, 15)),
+ ("2012-4-9 4:8:16", datetime(2012, 4, 9, 4, 8, 16)),
+ ("2012-04-23T09:15:00Z", datetime(2012, 4, 23, 9, 15, 0, 0, timezone.utc)),
+ ("2012-4-9 4:8:16-0320", datetime(2012, 4, 9, 4, 8, 16, 0, create_tz(-200))),
+ ("2012-04-23T10:20:30.400+02:30", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(150))),
+ ("2012-04-23T10:20:30.400+02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(120))),
+ ("2012-04-23T10:20:30.400-02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))),
+ (b"2012-04-23T10:20:30.400-02", datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))),
+ (datetime(2017, 5, 5), datetime(2017, 5, 5)),
+ (0, datetime(1970, 1, 1, 0, 0, 0, tzinfo=timezone.utc)),
+ # Invalid inputs
+ ("x20120423091500", ValueError),
+ ("2012-04-56T09:15:90", ValueError),
+ ("2012-04-23T11:05:00-25:00", ValueError),
+ (19_999_999_999, datetime(2603, 10, 11, 11, 33, 19, tzinfo=timezone.utc)), # just before watershed
+ (20_000_000_001, datetime(1970, 8, 20, 11, 33, 20, 1000, tzinfo=timezone.utc)), # just after watershed
+ (1_549_316_052, datetime(2019, 2, 4, 21, 34, 12, 0, tzinfo=timezone.utc)), # nowish in s
+ (1_549_316_052_104, datetime(2019, 2, 4, 21, 34, 12, 104_000, tzinfo=timezone.utc)), # nowish in ms
+ (1_549_316_052_104_324, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in μs
+ (1_549_316_052_104_324_096, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in ns
+ ("infinity", datetime(9999, 12, 31, 23, 59, 59, 999999)),
+ ("inf", datetime(9999, 12, 31, 23, 59, 59, 999999)),
+ ("inf ", datetime(9999, 12, 31, 23, 59, 59, 999999)),
+ (1e50, datetime(9999, 12, 31, 23, 59, 59, 999999)),
+ (float("inf"), datetime(9999, 12, 31, 23, 59, 59, 999999)),
+ ("-infinity", datetime(1, 1, 1, 0, 0)),
+ ("-inf", datetime(1, 1, 1, 0, 0)),
+ ("nan", ValueError),
+ ],
+)
+def test_datetime_parsing(value: Union[str, bytes, int, float], result: Union[datetime, Type[Exception]]) -> None:
+ if type(result) == type and issubclass(result, Exception): # pyright: ignore[reportUnnecessaryIsInstance]
+ with pytest.raises(result):
+ parse_datetime(value)
+ else:
+ assert parse_datetime(value) == result
diff --git a/tests/test_utils/test_json.py b/tests/test_utils/test_json.py
new file mode 100644
index 0000000..119963f
--- /dev/null
+++ b/tests/test_utils/test_json.py
@@ -0,0 +1,126 @@
+from __future__ import annotations
+
+import datetime
+from typing import Union
+
+import pydantic
+
+from opencode import _compat
+from opencode._utils._json import openapi_dumps
+
+
+class TestOpenapiDumps:
+ def test_basic(self) -> None:
+ data = {"key": "value", "number": 42}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"key":"value","number":42}'
+
+ def test_datetime_serialization(self) -> None:
+ dt = datetime.datetime(2023, 1, 1, 12, 0, 0)
+ data = {"datetime": dt}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"datetime":"2023-01-01T12:00:00"}'
+
+ def test_pydantic_model_serialization(self) -> None:
+ class User(pydantic.BaseModel):
+ first_name: str
+ last_name: str
+ age: int
+
+ model_instance = User(first_name="John", last_name="Kramer", age=83)
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"first_name":"John","last_name":"Kramer","age":83}}'
+
+ def test_pydantic_model_with_default_values(self) -> None:
+ class User(pydantic.BaseModel):
+ name: str
+ role: str = "user"
+ active: bool = True
+ score: int = 0
+
+ model_instance = User(name="Alice")
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Alice"}}'
+
+ def test_pydantic_model_with_default_values_overridden(self) -> None:
+ class User(pydantic.BaseModel):
+ name: str
+ role: str = "user"
+ active: bool = True
+
+ model_instance = User(name="Bob", role="admin", active=False)
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Bob","role":"admin","active":false}}'
+
+ def test_pydantic_model_with_alias(self) -> None:
+ class User(pydantic.BaseModel):
+ first_name: str = pydantic.Field(alias="firstName")
+ last_name: str = pydantic.Field(alias="lastName")
+
+ model_instance = User(firstName="John", lastName="Doe")
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"firstName":"John","lastName":"Doe"}}'
+
+ def test_pydantic_model_with_alias_and_default(self) -> None:
+ class User(pydantic.BaseModel):
+ user_name: str = pydantic.Field(alias="userName")
+ user_role: str = pydantic.Field(default="member", alias="userRole")
+ is_active: bool = pydantic.Field(default=True, alias="isActive")
+
+ model_instance = User(userName="charlie")
+ data = {"model": model_instance}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"userName":"charlie"}}'
+
+ model_with_overrides = User(userName="diana", userRole="admin", isActive=False)
+ data = {"model": model_with_overrides}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"userName":"diana","userRole":"admin","isActive":false}}'
+
+ def test_pydantic_model_with_nested_models_and_defaults(self) -> None:
+ class Address(pydantic.BaseModel):
+ street: str
+ city: str = "Unknown"
+
+ class User(pydantic.BaseModel):
+ name: str
+ address: Address
+ verified: bool = False
+
+ if _compat.PYDANTIC_V1:
+ # to handle forward references in Pydantic v1
+ User.update_forward_refs(**locals()) # type: ignore[reportDeprecated]
+
+ address = Address(street="123 Main St")
+ user = User(name="Diana", address=address)
+ data = {"user": user}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"user":{"name":"Diana","address":{"street":"123 Main St"}}}'
+
+ address_with_city = Address(street="456 Oak Ave", city="Boston")
+ user_verified = User(name="Eve", address=address_with_city, verified=True)
+ data = {"user": user_verified}
+ json_bytes = openapi_dumps(data)
+ assert (
+ json_bytes == b'{"user":{"name":"Eve","address":{"street":"456 Oak Ave","city":"Boston"},"verified":true}}'
+ )
+
+ def test_pydantic_model_with_optional_fields(self) -> None:
+ class User(pydantic.BaseModel):
+ name: str
+ email: Union[str, None]
+ phone: Union[str, None]
+
+ model_with_none = User(name="Eve", email=None, phone=None)
+ data = {"model": model_with_none}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Eve","email":null,"phone":null}}'
+
+ model_with_values = User(name="Frank", email="frank@example.com", phone=None)
+ data = {"model": model_with_values}
+ json_bytes = openapi_dumps(data)
+ assert json_bytes == b'{"model":{"name":"Frank","email":"frank@example.com","phone":null}}'
diff --git a/tests/test_utils/test_path.py b/tests/test_utils/test_path.py
new file mode 100644
index 0000000..87feb00
--- /dev/null
+++ b/tests/test_utils/test_path.py
@@ -0,0 +1,89 @@
+from __future__ import annotations
+
+from typing import Any
+
+import pytest
+
+from opencode._utils._path import path_template
+
+
+@pytest.mark.parametrize(
+ "template, kwargs, expected",
+ [
+ ("/v1/{id}", dict(id="abc"), "/v1/abc"),
+ ("/v1/{a}/{b}", dict(a="x", b="y"), "/v1/x/y"),
+ ("/v1/{a}{b}/path/{c}?val={d}#{e}", dict(a="x", b="y", c="z", d="u", e="v"), "/v1/xy/path/z?val=u#v"),
+ ("/{w}/{w}", dict(w="echo"), "/echo/echo"),
+ ("/v1/static", {}, "/v1/static"),
+ ("", {}, ""),
+ ("/v1/?q={n}&count=10", dict(n=42), "/v1/?q=42&count=10"),
+ ("/v1/{v}", dict(v=None), "/v1/null"),
+ ("/v1/{v}", dict(v=True), "/v1/true"),
+ ("/v1/{v}", dict(v=False), "/v1/false"),
+ ("/v1/{v}", dict(v=".hidden"), "/v1/.hidden"), # dot prefix ok
+ ("/v1/{v}", dict(v="file.txt"), "/v1/file.txt"), # dot in middle ok
+ ("/v1/{v}", dict(v="..."), "/v1/..."), # triple dot ok
+ ("/v1/{a}{b}", dict(a=".", b="txt"), "/v1/.txt"), # dot var combining with adjacent to be ok
+ ("/items?q={v}#{f}", dict(v=".", f=".."), "/items?q=.#.."), # dots in query/fragment are fine
+ (
+ "/v1/{a}?query={b}",
+ dict(a="../../other/endpoint", b="a&bad=true"),
+ "/v1/..%2F..%2Fother%2Fendpoint?query=a%26bad%3Dtrue",
+ ),
+ ("/v1/{val}", dict(val="a/b/c"), "/v1/a%2Fb%2Fc"),
+ ("/v1/{val}", dict(val="a/b/c?query=value"), "/v1/a%2Fb%2Fc%3Fquery=value"),
+ ("/v1/{val}", dict(val="a/b/c?query=value&bad=true"), "/v1/a%2Fb%2Fc%3Fquery=value&bad=true"),
+ ("/v1/{val}", dict(val="%20"), "/v1/%2520"), # escapes escape sequences in input
+ # Query: slash and ? are safe, # is not
+ ("/items?q={v}", dict(v="a/b"), "/items?q=a/b"),
+ ("/items?q={v}", dict(v="a?b"), "/items?q=a?b"),
+ ("/items?q={v}", dict(v="a#b"), "/items?q=a%23b"),
+ ("/items?q={v}", dict(v="a b"), "/items?q=a%20b"),
+ # Fragment: slash and ? are safe
+ ("/docs#{v}", dict(v="a/b"), "/docs#a/b"),
+ ("/docs#{v}", dict(v="a?b"), "/docs#a?b"),
+ # Path: slash, ? and # are all encoded
+ ("/v1/{v}", dict(v="a/b"), "/v1/a%2Fb"),
+ ("/v1/{v}", dict(v="a?b"), "/v1/a%3Fb"),
+ ("/v1/{v}", dict(v="a#b"), "/v1/a%23b"),
+ # same var encoded differently by component
+ (
+ "/v1/{v}?q={v}#{v}",
+ dict(v="a/b?c#d"),
+ "/v1/a%2Fb%3Fc%23d?q=a/b?c%23d#a/b?c%23d",
+ ),
+ ("/v1/{val}", dict(val="x?admin=true"), "/v1/x%3Fadmin=true"), # query injection
+ ("/v1/{val}", dict(val="x#admin"), "/v1/x%23admin"), # fragment injection
+ ],
+)
+def test_interpolation(template: str, kwargs: dict[str, Any], expected: str) -> None:
+ assert path_template(template, **kwargs) == expected
+
+
+def test_missing_kwarg_raises_key_error() -> None:
+ with pytest.raises(KeyError, match="org_id"):
+ path_template("/v1/{org_id}")
+
+
+@pytest.mark.parametrize(
+ "template, kwargs",
+ [
+ ("{a}/path", dict(a=".")),
+ ("{a}/path", dict(a="..")),
+ ("/v1/{a}", dict(a=".")),
+ ("/v1/{a}", dict(a="..")),
+ ("/v1/{a}/path", dict(a=".")),
+ ("/v1/{a}/path", dict(a="..")),
+ ("/v1/{a}{b}", dict(a=".", b=".")), # adjacent vars → ".."
+ ("/v1/{a}.", dict(a=".")), # var + static → ".."
+ ("/v1/{a}{b}", dict(a="", b=".")), # empty + dot → "."
+ ("/v1/%2e/{x}", dict(x="ok")), # encoded dot in static text
+ ("/v1/%2e./{x}", dict(x="ok")), # mixed encoded ".." in static
+ ("/v1/.%2E/{x}", dict(x="ok")), # mixed encoded ".." in static
+ ("/v1/{v}?q=1", dict(v="..")),
+ ("/v1/{v}#frag", dict(v="..")),
+ ],
+)
+def test_dot_segment_rejected(template: str, kwargs: dict[str, Any]) -> None:
+ with pytest.raises(ValueError, match="dot-segment"):
+ path_template(template, **kwargs)
diff --git a/tests/test_utils/test_proxy.py b/tests/test_utils/test_proxy.py
new file mode 100644
index 0000000..f4e2025
--- /dev/null
+++ b/tests/test_utils/test_proxy.py
@@ -0,0 +1,34 @@
+import operator
+from typing import Any
+from typing_extensions import override
+
+from opencode._utils import LazyProxy
+
+
+class RecursiveLazyProxy(LazyProxy[Any]):
+ @override
+ def __load__(self) -> Any:
+ return self
+
+ def __call__(self, *_args: Any, **_kwds: Any) -> Any:
+ raise RuntimeError("This should never be called!")
+
+
+def test_recursive_proxy() -> None:
+ proxy = RecursiveLazyProxy()
+ assert repr(proxy) == "RecursiveLazyProxy"
+ assert str(proxy) == "RecursiveLazyProxy"
+ assert dir(proxy) == []
+ assert type(proxy).__name__ == "RecursiveLazyProxy"
+ assert type(operator.attrgetter("name.foo.bar.baz")(proxy)).__name__ == "RecursiveLazyProxy"
+
+
+def test_isinstance_does_not_error() -> None:
+ class AlwaysErrorProxy(LazyProxy[Any]):
+ @override
+ def __load__(self) -> Any:
+ raise RuntimeError("Mocking missing dependency")
+
+ proxy = AlwaysErrorProxy()
+ assert not isinstance(proxy, dict)
+ assert isinstance(proxy, LazyProxy)
diff --git a/tests/test_utils/test_typing.py b/tests/test_utils/test_typing.py
new file mode 100644
index 0000000..adffcc2
--- /dev/null
+++ b/tests/test_utils/test_typing.py
@@ -0,0 +1,73 @@
+from __future__ import annotations
+
+from typing import Generic, TypeVar, cast
+
+from opencode._utils import extract_type_var_from_base
+
+_T = TypeVar("_T")
+_T2 = TypeVar("_T2")
+_T3 = TypeVar("_T3")
+
+
+class BaseGeneric(Generic[_T]): ...
+
+
+class SubclassGeneric(BaseGeneric[_T]): ...
+
+
+class BaseGenericMultipleTypeArgs(Generic[_T, _T2, _T3]): ...
+
+
+class SubclassGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T, _T2, _T3]): ...
+
+
+class SubclassDifferentOrderGenericMultipleTypeArgs(BaseGenericMultipleTypeArgs[_T2, _T, _T3]): ...
+
+
+def test_extract_type_var() -> None:
+ assert (
+ extract_type_var_from_base(
+ BaseGeneric[int],
+ index=0,
+ generic_bases=cast("tuple[type, ...]", (BaseGeneric,)),
+ )
+ == int
+ )
+
+
+def test_extract_type_var_generic_subclass() -> None:
+ assert (
+ extract_type_var_from_base(
+ SubclassGeneric[int],
+ index=0,
+ generic_bases=cast("tuple[type, ...]", (BaseGeneric,)),
+ )
+ == int
+ )
+
+
+def test_extract_type_var_multiple() -> None:
+ typ = BaseGenericMultipleTypeArgs[int, str, None]
+
+ generic_bases = cast("tuple[type, ...]", (BaseGenericMultipleTypeArgs,))
+ assert extract_type_var_from_base(typ, index=0, generic_bases=generic_bases) == int
+ assert extract_type_var_from_base(typ, index=1, generic_bases=generic_bases) == str
+ assert extract_type_var_from_base(typ, index=2, generic_bases=generic_bases) == type(None)
+
+
+def test_extract_type_var_generic_subclass_multiple() -> None:
+ typ = SubclassGenericMultipleTypeArgs[int, str, None]
+
+ generic_bases = cast("tuple[type, ...]", (BaseGenericMultipleTypeArgs,))
+ assert extract_type_var_from_base(typ, index=0, generic_bases=generic_bases) == int
+ assert extract_type_var_from_base(typ, index=1, generic_bases=generic_bases) == str
+ assert extract_type_var_from_base(typ, index=2, generic_bases=generic_bases) == type(None)
+
+
+def test_extract_type_var_generic_subclass_different_ordering_multiple() -> None:
+ typ = SubclassDifferentOrderGenericMultipleTypeArgs[int, str, None]
+
+ generic_bases = cast("tuple[type, ...]", (BaseGenericMultipleTypeArgs,))
+ assert extract_type_var_from_base(typ, index=0, generic_bases=generic_bases) == int
+ assert extract_type_var_from_base(typ, index=1, generic_bases=generic_bases) == str
+ assert extract_type_var_from_base(typ, index=2, generic_bases=generic_bases) == type(None)
diff --git a/tests/utils.py b/tests/utils.py
new file mode 100644
index 0000000..666a129
--- /dev/null
+++ b/tests/utils.py
@@ -0,0 +1,167 @@
+from __future__ import annotations
+
+import os
+import inspect
+import traceback
+import contextlib
+from typing import Any, TypeVar, Iterator, Sequence, cast
+from datetime import date, datetime
+from typing_extensions import Literal, get_args, get_origin, assert_type
+
+from opencode._types import Omit, NoneType
+from opencode._utils import (
+ is_dict,
+ is_list,
+ is_list_type,
+ is_union_type,
+ extract_type_arg,
+ is_sequence_type,
+ is_annotated_type,
+ is_type_alias_type,
+)
+from opencode._compat import PYDANTIC_V1, field_outer_type, get_model_fields
+from opencode._models import BaseModel
+
+BaseModelT = TypeVar("BaseModelT", bound=BaseModel)
+
+
+def assert_matches_model(model: type[BaseModelT], value: BaseModelT, *, path: list[str]) -> bool:
+ for name, field in get_model_fields(model).items():
+ field_value = getattr(value, name)
+ if PYDANTIC_V1:
+ # in v1 nullability was structured differently
+ # https://docs.pydantic.dev/2.0/migration/#required-optional-and-nullable-fields
+ allow_none = getattr(field, "allow_none", False)
+ else:
+ allow_none = False
+
+ assert_matches_type(
+ field_outer_type(field),
+ field_value,
+ path=[*path, name],
+ allow_none=allow_none,
+ )
+
+ return True
+
+
+# Note: the `path` argument is only used to improve error messages when `--showlocals` is used
+def assert_matches_type(
+ type_: Any,
+ value: object,
+ *,
+ path: list[str],
+ allow_none: bool = False,
+) -> None:
+ if is_type_alias_type(type_):
+ type_ = type_.__value__
+
+ # unwrap `Annotated[T, ...]` -> `T`
+ if is_annotated_type(type_):
+ type_ = extract_type_arg(type_, 0)
+
+ if allow_none and value is None:
+ return
+
+ if type_ is None or type_ is NoneType:
+ assert value is None
+ return
+
+ origin = get_origin(type_) or type_
+
+ if is_list_type(type_):
+ return _assert_list_type(type_, value)
+
+ if is_sequence_type(type_):
+ assert isinstance(value, Sequence)
+ inner_type = get_args(type_)[0]
+ for entry in value: # type: ignore
+ assert_type(inner_type, entry) # type: ignore
+ return
+
+ if origin == str:
+ assert isinstance(value, str)
+ elif origin == int:
+ assert isinstance(value, int)
+ elif origin == bool:
+ assert isinstance(value, bool)
+ elif origin == float:
+ assert isinstance(value, float)
+ elif origin == bytes:
+ assert isinstance(value, bytes)
+ elif origin == datetime:
+ assert isinstance(value, datetime)
+ elif origin == date:
+ assert isinstance(value, date)
+ elif origin == object:
+ # nothing to do here, the expected type is unknown
+ pass
+ elif origin == Literal:
+ assert value in get_args(type_)
+ elif origin == dict:
+ assert is_dict(value)
+
+ args = get_args(type_)
+ key_type = args[0]
+ items_type = args[1]
+
+ for key, item in value.items():
+ assert_matches_type(key_type, key, path=[*path, ""])
+ assert_matches_type(items_type, item, path=[*path, ""])
+ elif is_union_type(type_):
+ variants = get_args(type_)
+
+ try:
+ none_index = variants.index(type(None))
+ except ValueError:
+ pass
+ else:
+ # special case Optional[T] for better error messages
+ if len(variants) == 2:
+ if value is None:
+ # valid
+ return
+
+ return assert_matches_type(type_=variants[not none_index], value=value, path=path)
+
+ for i, variant in enumerate(variants):
+ try:
+ assert_matches_type(variant, value, path=[*path, f"variant {i}"])
+ return
+ except AssertionError:
+ traceback.print_exc()
+ continue
+
+ raise AssertionError("Did not match any variants")
+ elif issubclass(origin, BaseModel):
+ assert isinstance(value, type_)
+ assert assert_matches_model(type_, cast(Any, value), path=path)
+ elif inspect.isclass(origin) and origin.__name__ == "HttpxBinaryResponseContent":
+ assert value.__class__.__name__ == "HttpxBinaryResponseContent"
+ else:
+ assert None, f"Unhandled field type: {type_}"
+
+
+def _assert_list_type(type_: type[object], value: object) -> None:
+ assert is_list(value)
+
+ inner_type = get_args(type_)[0]
+ for entry in value:
+ assert_type(inner_type, entry) # type: ignore
+
+
+@contextlib.contextmanager
+def update_env(**new_env: str | Omit) -> Iterator[None]:
+ old = os.environ.copy()
+
+ try:
+ for name, value in new_env.items():
+ if isinstance(value, Omit):
+ os.environ.pop(name, None)
+ else:
+ os.environ[name] = value
+
+ yield None
+ finally:
+ os.environ.clear()
+ os.environ.update(old)
From fcaa3425a39c7e417ef77c1c352fca9dd737475d Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 16:02:01 +0530
Subject: [PATCH 02/41] Add the dependencies of Ferrule's runtime and generated
tests
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
pyproject.toml | 8 +-
src/opencode/_version.py | 2 +
tests/{ => custom}/fake_service.py | 0
tests/{ => custom}/test_events.py | 0
tests/{ => custom}/test_helpers.py | 0
tests/{ => custom}/test_live.py | 0
tests/{ => custom}/test_pagination.py | 0
tests/{ => custom}/test_pty.py | 0
tests/{ => custom}/test_rpc.py | 0
tests/{ => custom}/test_service.py | 0
tests/{ => custom}/test_service_live.py | 0
tests/test_aiohttp.py | 131 ------------
tests/test_async_client.py | 51 -----
tests/test_docs_coverage.py | 256 ------------------------
tests/test_encoding.py | 56 ------
tests/test_logs.py | 45 -----
tests/test_with_options.py | 76 -------
uv.lock | 199 +++++++++++++++++-
18 files changed, 207 insertions(+), 617 deletions(-)
rename tests/{ => custom}/fake_service.py (100%)
rename tests/{ => custom}/test_events.py (100%)
rename tests/{ => custom}/test_helpers.py (100%)
rename tests/{ => custom}/test_live.py (100%)
rename tests/{ => custom}/test_pagination.py (100%)
rename tests/{ => custom}/test_pty.py (100%)
rename tests/{ => custom}/test_rpc.py (100%)
rename tests/{ => custom}/test_service.py (100%)
rename tests/{ => custom}/test_service_live.py (100%)
delete mode 100644 tests/test_aiohttp.py
delete mode 100644 tests/test_async_client.py
delete mode 100644 tests/test_docs_coverage.py
delete mode 100644 tests/test_encoding.py
delete mode 100644 tests/test_logs.py
delete mode 100644 tests/test_with_options.py
diff --git a/pyproject.toml b/pyproject.toml
index b6c2ad9..6def56d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -27,9 +27,11 @@ classifiers = [
]
dependencies = [
"anyio>=4.4,<5",
+ "distro>=1.7,<2",
"httpx>=0.27,<1",
"pydantic>=2.12,<3",
- "typing-extensions>=4.12",
+ "sniffio",
+ "typing-extensions>=4.14,<5",
]
[project.urls]
@@ -44,18 +46,22 @@ pty = [
"websockets>=14,<17",
]
aiohttp = [
+ "aiohttp",
"httpx-aiohttp>=0.1.9,<0.3",
]
[dependency-groups]
dev = [
+ "dirty-equals>=0.6",
"mypy>=1.18",
"pyright>=1.1.408",
"pytest>=8.4",
+ "pytest-asyncio>=1",
"httpx-aiohttp>=0.1.9",
"pytest-cov>=7",
"respx>=0.22",
"ruff>=0.14",
+ "time-machine>=2",
"trio>=0.31",
"websockets>=14",
]
diff --git a/src/opencode/_version.py b/src/opencode/_version.py
index 75a5d03..f3a4fe8 100644
--- a/src/opencode/_version.py
+++ b/src/opencode/_version.py
@@ -2,6 +2,8 @@
from importlib.metadata import PackageNotFoundError, version
+__title__ = "opencode"
+
try:
__version__ = version("opencode-python-sdk")
except PackageNotFoundError: # running from a source tree that is not installed
diff --git a/tests/fake_service.py b/tests/custom/fake_service.py
similarity index 100%
rename from tests/fake_service.py
rename to tests/custom/fake_service.py
diff --git a/tests/test_events.py b/tests/custom/test_events.py
similarity index 100%
rename from tests/test_events.py
rename to tests/custom/test_events.py
diff --git a/tests/test_helpers.py b/tests/custom/test_helpers.py
similarity index 100%
rename from tests/test_helpers.py
rename to tests/custom/test_helpers.py
diff --git a/tests/test_live.py b/tests/custom/test_live.py
similarity index 100%
rename from tests/test_live.py
rename to tests/custom/test_live.py
diff --git a/tests/test_pagination.py b/tests/custom/test_pagination.py
similarity index 100%
rename from tests/test_pagination.py
rename to tests/custom/test_pagination.py
diff --git a/tests/test_pty.py b/tests/custom/test_pty.py
similarity index 100%
rename from tests/test_pty.py
rename to tests/custom/test_pty.py
diff --git a/tests/test_rpc.py b/tests/custom/test_rpc.py
similarity index 100%
rename from tests/test_rpc.py
rename to tests/custom/test_rpc.py
diff --git a/tests/test_service.py b/tests/custom/test_service.py
similarity index 100%
rename from tests/test_service.py
rename to tests/custom/test_service.py
diff --git a/tests/test_service_live.py b/tests/custom/test_service_live.py
similarity index 100%
rename from tests/test_service_live.py
rename to tests/custom/test_service_live.py
diff --git a/tests/test_aiohttp.py b/tests/test_aiohttp.py
deleted file mode 100644
index ff7cae3..0000000
--- a/tests/test_aiohttp.py
+++ /dev/null
@@ -1,131 +0,0 @@
-"""`DefaultAioHttpClient` against a real aiohttp server. Skipped unless the ``aiohttp`` extra is installed."""
-
-from __future__ import annotations
-
-import asyncio
-import importlib
-import sys
-from collections.abc import AsyncIterator, Awaitable, Callable
-
-import pytest
-
-from opencode import APITimeoutError, AsyncOpencode, DefaultAioHttpClient
-from opencode.types import ServerConnectedEvent
-
-pytestmark = pytest.mark.anyio
-
-SESSION = {
- "id": "ses_1",
- "projectID": "prj_1",
- "cost": 0,
- "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}},
- "time": {"created": 1, "updated": 1},
- "title": "Hello",
- "location": {"directory": "/repo"},
-}
-CONNECTED = '{"type":"server.connected","id":"evt_1","created":1,"data":{}}'
-
-
-def test_missing_extra_raises(monkeypatch: pytest.MonkeyPatch) -> None:
- monkeypatch.setitem(sys.modules, "httpx_aiohttp", None)
- module = importlib.reload(importlib.import_module("opencode._aiohttp"))
- try:
- with pytest.raises(RuntimeError, match=r"opencode-python-sdk\[aiohttp\]"):
- module.DefaultAioHttpClient()
- finally:
- monkeypatch.undo()
- importlib.reload(module)
-
-
-@pytest.fixture
-async def server_url() -> AsyncIterator[str]:
- pytest.importorskip("httpx_aiohttp")
- from aiohttp import web
- from aiohttp.test_utils import TestServer
-
- attempts = {"retry": 0}
-
- async def session(request: web.Request) -> web.Response:
- body = await request.json()
- return web.json_response({"data": {**SESSION, "title": body["title"]}})
-
- async def flaky(request: web.Request) -> web.Response:
- attempts["retry"] += 1
- if attempts["retry"] == 1:
- return web.json_response({"_tag": "UnknownError", "message": "busy"}, status=503)
- return web.json_response({"data": {**SESSION, "title": "retried"}})
-
- async def slow(request: web.Request) -> web.Response:
- await asyncio.sleep(5)
- return web.json_response({"data": SESSION})
-
- async def events(request: web.Request) -> web.StreamResponse:
- response = web.StreamResponse(headers={"Content-Type": "text/event-stream"})
- await response.prepare(request)
- await response.write(b": hi\n\n")
- # Split one event across writes to check the decoder reassembles aiohttp's chunks.
- await response.write(f"data: {CONNECTED[:20]}".encode())
- await response.write(f"{CONNECTED[20:]}\n\n".encode())
- await response.write_eof()
- return response
-
- modes = {"retry": flaky, "slow": slow}
-
- # The retry and timeout cases reuse the session route, picked by a header.
- @web.middleware
- async def switch(
- request: web.Request, handler: Callable[[web.Request], Awaitable[web.StreamResponse]]
- ) -> web.StreamResponse:
- mode = request.headers.get("X-Test-Mode")
- return await modes[mode](request) if mode else await handler(request)
-
- app = web.Application(middlewares=[switch])
- app.router.add_post("/api/session", session)
- app.router.add_get("/api/event", events)
- server = TestServer(app, host="127.0.0.1")
- await server.start_server()
- try:
- yield str(server.make_url("/"))
- finally:
- await server.close()
-
-
-async def test_request_and_body(server_url: str) -> None:
- async with DefaultAioHttpClient() as http, AsyncOpencode(server_url, password="", http_client=http) as client:
- session = await client.session.create(title="Hello")
- assert session.title == "Hello"
-
-
-async def test_event_stream(server_url: str) -> None:
- async with DefaultAioHttpClient() as http, AsyncOpencode(server_url, password="", http_client=http) as client: # noqa: SIM117
- async with await client.event.subscribe() as stream:
- received = [event async for event in stream]
- assert len(received) == 1
- assert isinstance(received[0], ServerConnectedEvent)
-
-
-async def test_retries_server_errors(server_url: str) -> None:
- async with (
- DefaultAioHttpClient() as http,
- AsyncOpencode(
- server_url, password="", http_client=http, max_retries=2, default_headers={"X-Test-Mode": "retry"}
- ) as client,
- ):
- session = await client.session.create(title="ignored")
- assert session.title == "retried"
-
-
-async def test_timeout(server_url: str) -> None:
- async with (
- DefaultAioHttpClient() as http,
- AsyncOpencode(
- server_url,
- password="",
- http_client=http,
- max_retries=0,
- timeout=0.2,
- default_headers={"X-Test-Mode": "slow"},
- ) as client,
- ):
- with pytest.raises(APITimeoutError):
- await client.session.create(title="slow")
diff --git a/tests/test_async_client.py b/tests/test_async_client.py
deleted file mode 100644
index a1f1f36..0000000
--- a/tests/test_async_client.py
+++ /dev/null
@@ -1,51 +0,0 @@
-from __future__ import annotations
-
-import json
-
-import pytest
-import respx
-
-from opencode import AsyncOpencode, SessionBusyError
-
-pytestmark = pytest.mark.anyio
-
-SESSION = {
- "id": "ses_1",
- "projectID": "prj_1",
- "cost": 0,
- "tokens": {"input": 0, "output": 0, "reasoning": 0, "cache": {"read": 0, "write": 0}},
- "time": {"created": 1, "updated": 1},
- "title": "Hello",
- "location": {"directory": "/repo"},
-}
-
-
-@pytest.fixture(params=["asyncio", "trio"])
-def anyio_backend(request: pytest.FixtureRequest) -> str:
- return str(request.param)
-
-
-async def test_async_request_and_body(mock_api: respx.MockRouter) -> None:
- route = mock_api.post("/api/session").respond(json={"data": SESSION})
- async with AsyncOpencode("http://opencode.test", password="") as client:
- session = await client.session.create(title="Hello")
- assert session.title == "Hello"
- assert json.loads(route.calls.last.request.content) == {"title": "Hello"}
-
-
-async def test_async_tagged_error(mock_api: respx.MockRouter) -> None:
- mock_api.post("/api/session/ses_1/prompt").respond(
- 409, json={"_tag": "SessionBusyError", "sessionID": "ses_1", "message": "busy"}
- )
- async with AsyncOpencode("http://opencode.test", password="") as client:
- with pytest.raises(SessionBusyError):
- await client.session.prompt("ses_1", text="hi")
-
-
-async def test_async_retry(mock_api: respx.MockRouter) -> None:
- route = mock_api.get("/api/agent").mock(
- side_effect=[respx.MockResponse(503), respx.MockResponse(json={"location": {"directory": "/"}, "data": []})]
- )
- async with AsyncOpencode("http://opencode.test", password="") as client:
- await client.agent.list()
- assert route.call_count == 2
diff --git a/tests/test_docs_coverage.py b/tests/test_docs_coverage.py
deleted file mode 100644
index 9c8242b..0000000
--- a/tests/test_docs_coverage.py
+++ /dev/null
@@ -1,256 +0,0 @@
-"""Fails when the public API and the documentation drift apart.
-
-Checks, in both directions:
-
-- every resource class and method reachable from the client is rendered in the API reference;
-- every name in ``opencode.__all__`` and ``opencode.types`` is rendered or mentioned in ``docs/``;
-- every resource group on the client is listed in ``docs/guides/index.md``;
-- guides only reference methods and types that exist.
-
-The API reference pages for resources and types are generated by ``docs/hooks/types_reference.py`` at build time,
-so these tests load that hook and check its output, plus the hand-written pages.
-"""
-
-from __future__ import annotations
-
-import builtins
-import importlib.util
-import inspect
-import re
-from pathlib import Path
-from types import ModuleType
-from typing import Any
-
-import pytest
-
-import opencode
-from opencode import resources as opencode_resources, types as opencode_types
-from opencode._resource import AsyncAPIResource, SyncAPIResource
-
-ROOT = Path(__file__).resolve().parents[1]
-DOCS = ROOT / "docs"
-HOOK = DOCS / "hooks/types_reference.py"
-GUIDE_INDEX = DOCS / "guides/index.md"
-
-DIRECTIVE = re.compile(r"^::: (\S+)((?:\n {4}.*)*)", re.M)
-MEMBERS_OPTION = re.compile(r"members:\s*\[([^\]]*)\]")
-
-
-def _load_hook() -> ModuleType:
- spec = importlib.util.spec_from_file_location("docs_hook", HOOK)
- assert spec is not None
- assert spec.loader is not None
- module = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(module)
- return module
-
-
-HOOK_MODULE = _load_hook()
-
-
-def _hand_written_pages() -> dict[str, str]:
- return {str(path.relative_to(DOCS)): path.read_text() for path in sorted(DOCS.rglob("*.md"))}
-
-
-def _generated_pages() -> dict[str, str]:
- pages = {"reference/resources.md (generated)": HOOK_MODULE.resources_page()}
- for title, names in HOOK_MODULE.type_groups().items():
- pages[f"reference/types/{title} (generated)"] = HOOK_MODULE.type_page(title, names)
- return pages
-
-
-HAND_WRITTEN = _hand_written_pages()
-ALL_PAGES = {**HAND_WRITTEN, **_generated_pages()}
-
-
-def _directives() -> dict[str, list[str] | None]:
- """Maps each ``:::`` target to its ``members`` option, or ``None`` when all members are rendered."""
- found: dict[str, list[str] | None] = {}
- for text in ALL_PAGES.values():
- for match in DIRECTIVE.finditer(text):
- members = MEMBERS_OPTION.search(match.group(2))
- found[match.group(1)] = (
- [m.strip().strip("'\"") for m in members.group(1).split(",") if m.strip()] if members else None
- )
- return found
-
-
-DIRECTIVES = _directives()
-
-
-def _client() -> opencode.Opencode:
- return opencode.Opencode("http://127.0.0.1:1", password="")
-
-
-def _resources(obj: object, path: str) -> list[tuple[str, SyncAPIResource]]:
- found: list[tuple[str, SyncAPIResource]] = []
- for name in dir(type(obj)):
- if name.startswith("_"):
- continue
- value = getattr(obj, name)
- if isinstance(value, SyncAPIResource):
- found.append((f"{path}.{name}", value))
- found += _resources(value, f"{path}.{name}")
- return found
-
-
-RESOURCES = _resources(_client(), "client")
-
-
-def _public_methods(cls: type) -> list[str]:
- return [
- name
- for name, value in vars(cls).items()
- if not name.startswith(("_", "model_")) and (inspect.isfunction(value) or isinstance(value, property))
- ]
-
-
-def _source(obj: object) -> str:
- path = inspect.getsourcefile(obj) # type: ignore[arg-type]
- return str(Path(path).relative_to(ROOT)) if path else "its module"
-
-
-def _target(cls: type) -> str:
- return f"{cls.__module__}.{cls.__qualname__}"
-
-
-@pytest.mark.parametrize(("path", "resource"), RESOURCES, ids=[path for path, _ in RESOURCES])
-def test_resource_is_rendered(path: str, resource: SyncAPIResource) -> None:
- cls = type(resource)
- assert _target(cls) in DIRECTIVES, (
- f"`{path}` ({_target(cls)}) is not in the API reference. Resource pages are generated by "
- f"docs/hooks/types_reference.py from src/opencode/resources/; check that hook still finds {cls.__name__}."
- )
- for name in _public_methods(cls):
- method = getattr(cls, name)
- assert inspect.getdoc(method), (
- f"`{path}.{name}()` has no docstring, so its reference entry is empty. Add one in codegen/render.py "
- "(generated resources) and run `uv run python -m codegen`."
- )
-
-
-@pytest.mark.parametrize(("path", "resource"), RESOURCES, ids=[path for path, _ in RESOURCES])
-def test_async_resource_matches_sync(path: str, resource: SyncAPIResource) -> None:
- """The docs only render sync resources and say the async twin has the same methods; keep that true."""
- sync_cls = type(resource)
- module = inspect.getmodule(sync_cls)
- async_cls = getattr(module, f"Async{sync_cls.__name__}", None)
- assert async_cls is not None, f"`{path}` has no async twin Async{sync_cls.__name__}"
- assert issubclass(async_cls, AsyncAPIResource)
- assert set(_public_methods(sync_cls)) == set(_public_methods(async_cls)), (
- f"`{path}`: {sync_cls.__name__} and {async_cls.__name__} expose different methods, but the docs say they match."
- )
-
-
-def _mentioned(name: str) -> bool:
- pattern = re.compile(rf"`{re.escape(name)}(?:\(\))?`|\bopencode\.{re.escape(name)}\b")
- return any(pattern.search(text) for text in HAND_WRITTEN.values())
-
-
-@pytest.mark.parametrize("name", opencode.__all__)
-def test_top_level_export_is_documented(name: str) -> None:
- obj = getattr(opencode, name)
- target = f"opencode.{name}"
- if inspect.isclass(obj) or inspect.isfunction(obj):
- assert target in DIRECTIVES, (
- f"`{target}` is exported but not rendered in the docs. Add `::: {target}` to the matching page under "
- "docs/reference/ (clients.md, server.md, pagination.md or exceptions.md)."
- )
- members = DIRECTIVES[target]
- if members is not None and inspect.isclass(obj):
- missing = [m for m in _public_methods(obj) if m not in members]
- assert not missing, (
- f"`{target}` is rendered with a `members:` list that leaves out {missing}. Add them to the "
- f"`::: {target}` directive in docs/reference/."
- )
- else:
- assert target in DIRECTIVES or _mentioned(name), (
- f"`{target}` is exported but never mentioned in docs/. Render it with `::: {target}` or mention "
- f"`{name}` in a guide."
- )
-
-
-@pytest.mark.parametrize("name", [n for n in opencode.__all__ if inspect.isclass(getattr(opencode, n))])
-def test_top_level_class_has_docstrings(name: str) -> None:
- """Ruff's D1 rules skip private modules, so the hand-written runtime (``_client.py``, ``_server.py``, ...) is
- checked here: every exported class and each of its own public methods and properties needs a docstring."""
- cls = getattr(opencode, name)
- assert cls.__doc__, f"`opencode.{name}` has no docstring. Add one in {_source(cls)}."
- missing = [m for m in _public_methods(cls) if not inspect.getdoc(vars(cls)[m])]
- assert not missing, f"`opencode.{name}` members without a docstring: {missing}. Add them in {_source(cls)}."
-
-
-def test_every_type_is_rendered() -> None:
- rendered = {name for names in HOOK_MODULE.type_groups().values() for name in names}
- exported = {name for name in dir(opencode_types) if not name.startswith("_") and name[0].isupper()}
- missing = sorted(exported - rendered)
- assert not missing, (
- f"These types are exported from opencode.types but not in the type reference: {missing}. The pages are "
- "generated by docs/hooks/types_reference.py from src/opencode/types/_generated.py; update the hook if "
- "types now live elsewhere."
- )
-
-
-def test_guide_index_lists_every_resource_group() -> None:
- text = GUIDE_INDEX.read_text()
- top_level = [path for path, _ in RESOURCES if path.count(".") == 1]
- missing = [path for path in top_level if f"`{path}`" not in text]
- assert not missing, (
- f"Resource groups missing from docs/guides/index.md: {missing}. Add a row for each, linking its "
- "reference entry (and a guide, if it needs one)."
- )
- listed = re.findall(r"^\| `(client\.\w+)`", text, re.M)
- stale = [path for path in listed if path not in top_level]
- assert not stale, f"docs/guides/index.md lists groups the client no longer has: {stale}. Remove those rows."
-
-
-# ---------- Stale references in hand-written pages ----------
-
-CALL = re.compile(r"\bclient((?:\.\w+)+)\(")
-FROM_IMPORT = re.compile(r"from opencode(\.types)? import ([\w ,]+)")
-QUALIFIED = re.compile(r"\bopencode\.([A-Z]\w+)")
-# Only CamelCase words with two or more humps, so `GET`, `DEBUG` or `Authorization` are not taken for types.
-BACKTICK_TYPE = re.compile(r"`([A-Z][a-z0-9]+(?:[A-Z][a-z0-9]*)+)`")
-
-# CamelCase names in prose that are not part of this package.
-EXTERNAL_NAMES = {"ClientApi", "HttpApi", "OpenCode", "TypeScript"}
-
-
-def _exists(name: str) -> bool:
- return (
- hasattr(opencode, name)
- or hasattr(opencode_types, name)
- or hasattr(opencode_resources, name)
- or hasattr(builtins, name)
- )
-
-
-def _stale_references() -> list[str]:
- client = _client()
- problems: list[str] = []
- for page, text in HAND_WRITTEN.items():
- for match in CALL.finditer(text):
- obj: Any = client
- for attr in match.group(1).strip(".").split("."):
- if not hasattr(obj, attr):
- problems.append(f"{page}: `client{match.group(1)}()` does not exist (no `{attr}`)")
- break
- obj = getattr(obj, attr)
- for match in FROM_IMPORT.finditer(text):
- module: ModuleType = opencode_types if match.group(1) else opencode
- for name in (n.strip() for n in match.group(2).split(",")):
- if name and not hasattr(module, name):
- problems.append(f"{page}: `{name}` is imported from {module.__name__} but does not exist")
- for match in QUALIFIED.finditer(text):
- if not hasattr(opencode, match.group(1)):
- problems.append(f"{page}: `opencode.{match.group(1)}` does not exist")
- for match in BACKTICK_TYPE.finditer(text):
- name = match.group(1)
- if name not in EXTERNAL_NAMES and not _exists(name):
- problems.append(f"{page}: `{name}` is not a name in opencode or opencode.types")
- return problems
-
-
-def test_guides_reference_only_existing_api() -> None:
- problems = _stale_references()
- assert not problems, "Docs reference API that does not exist. Fix or remove:\n" + "\n".join(problems)
diff --git a/tests/test_encoding.py b/tests/test_encoding.py
deleted file mode 100644
index 0965ee2..0000000
--- a/tests/test_encoding.py
+++ /dev/null
@@ -1,56 +0,0 @@
-from __future__ import annotations
-
-import pytest
-
-from opencode import NOT_GIVEN
-from opencode._qs import encode_path, encode_path_segment, encode_query, to_jsonable
-from opencode.types import Location, ModelRef
-
-
-def test_query_matches_typescript_client_rules() -> None:
- pairs = encode_query(
- {
- "limit": 20,
- "archived": False,
- "parentID": None,
- "skip": NOT_GIVEN,
- "type": ["user", "assistant"],
- "location": {"directory": "/repo"},
- }
- )
- assert pairs == [
- ("limit", "20"),
- ("archived", "false"),
- ("parentID", "null"),
- ("type", "user"),
- ("type", "assistant"),
- ("location[directory]", "/repo"),
- ]
-
-
-def test_query_serializes_models_by_wire_name() -> None:
- assert encode_query({"location": Location(directory="/srv/app")}) == [("location[directory]", "/srv/app")]
-
-
-def test_path_segment_escapes_reserved_characters() -> None:
- assert encode_path_segment("a/b?c#d") == "a%2Fb%3Fc%23d"
-
-
-@pytest.mark.parametrize("value", [".", ".."])
-def test_path_segment_encodes_dot_segments(value: str) -> None:
- assert "." not in encode_path_segment(value)
-
-
-def test_empty_path_segment_is_rejected() -> None:
- with pytest.raises(ValueError, match="must not be empty"):
- encode_path_segment("")
-
-
-def test_wildcard_path_keeps_separators_but_not_traversal() -> None:
- assert encode_path("src/my file.py") == "src/my%20file.py"
- assert encode_path("../etc/passwd") == "%2E%2E/etc/passwd"
-
-
-def test_to_jsonable_drops_not_given_and_uses_aliases() -> None:
- value = to_jsonable({"model": ModelRef(id="claude", provider_id="anthropic"), "skip": NOT_GIVEN, "raw": b"hi"})
- assert value == {"model": {"id": "claude", "providerID": "anthropic"}, "raw": "aGk="}
diff --git a/tests/test_logs.py b/tests/test_logs.py
deleted file mode 100644
index 00d21d8..0000000
--- a/tests/test_logs.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from __future__ import annotations
-
-import logging
-from collections.abc import Iterator
-
-import pytest
-
-from opencode._logs import logger, setup_logging
-
-
-@pytest.fixture(autouse=True)
-def _restore_logger() -> Iterator[None]:
- handlers, level = list(logger.handlers), logger.level
- yield
- logger.handlers[:] = handlers
- logger.setLevel(level)
-
-
-def _env_handlers() -> list[logging.Handler]:
- return [h for h in logger.handlers if getattr(h, "_opencode_env", False)]
-
-
-@pytest.mark.parametrize(
- ("value", "level"), [("debug", logging.DEBUG), ("INFO", logging.INFO), (" Debug ", logging.DEBUG)]
-)
-def test_opencode_log_sets_level(monkeypatch: pytest.MonkeyPatch, value: str, level: int) -> None:
- logger.handlers[:] = []
- monkeypatch.setenv("OPENCODE_LOG", value)
- setup_logging()
- setup_logging()
- assert logger.level == level
- assert len(_env_handlers()) == 1
-
-
-@pytest.mark.parametrize("value", [None, "", "warning", "verbose"])
-def test_opencode_log_ignored(monkeypatch: pytest.MonkeyPatch, value: str | None) -> None:
- logger.handlers[:] = []
- logger.setLevel(logging.NOTSET)
- if value is None:
- monkeypatch.delenv("OPENCODE_LOG", raising=False)
- else:
- monkeypatch.setenv("OPENCODE_LOG", value)
- setup_logging()
- assert logger.level == logging.NOTSET
- assert _env_handlers() == []
diff --git a/tests/test_with_options.py b/tests/test_with_options.py
deleted file mode 100644
index af57a4c..0000000
--- a/tests/test_with_options.py
+++ /dev/null
@@ -1,76 +0,0 @@
-from __future__ import annotations
-
-import pytest
-import respx
-
-from opencode import AsyncOpencode, Opencode
-
-from .conftest import BASE_URL
-
-AGENTS: dict[str, object] = {"location": {"directory": "/repo"}, "data": []}
-
-
-@pytest.mark.parametrize("method", ["with_options", "copy"])
-def test_overrides_apply_and_original_unchanged(mock_api: respx.MockRouter, method: str) -> None:
- route = mock_api.get("/api/agent").respond(json=AGENTS)
- with Opencode(
- BASE_URL, password="pw", directory="/repo", timeout=5.0, max_retries=3, default_headers={"X-A": "1"}
- ) as client:
- other = getattr(client, method)(
- password="other",
- directory="/elsewhere",
- timeout=1.0,
- max_retries=0,
- default_headers={"X-B": "2"},
- default_query={"q": "2"},
- )
- assert isinstance(other, Opencode)
- assert (other.max_retries, other.timeout, other.directory, other.password) == (0, 1.0, "/elsewhere", "other")
- other.agent.list()
- request = route.calls.last.request
- assert request.headers["X-A"] == "1"
- assert request.headers["X-B"] == "2"
- assert request.url.params["q"] == "2"
- assert request.url.params["location[directory]"] == "/elsewhere"
-
- assert (client.max_retries, client.timeout, client.directory, client.password) == (3, 5.0, "/repo", "pw")
- assert "X-B" not in client._default_headers
-
-
-def test_base_url_override(mock_api: respx.MockRouter) -> None:
- with Opencode(BASE_URL, password="") as client:
- other = client.with_options(base_url="http://other.test")
- assert other.base_url.host == "other.test"
- assert str(client.base_url).startswith(BASE_URL)
-
-
-def test_copy_does_not_close_shared_pool(mock_api: respx.MockRouter) -> None:
- route = mock_api.get("/api/agent").respond(json=AGENTS)
- client = Opencode(BASE_URL, password="")
- with client.copy(max_retries=0) as other:
- assert other._http is client._http
- assert not client._http.is_closed
- client.agent.list()
- assert route.called
- client.close()
- assert client._http.is_closed
-
-
-@pytest.mark.anyio
-@pytest.mark.parametrize("method", ["with_options", "copy"])
-async def test_async_overrides_and_shared_pool(mock_api: respx.MockRouter, method: str) -> None:
- route = mock_api.get("/api/agent").respond(json=AGENTS)
- client = AsyncOpencode(BASE_URL, password="", directory="/repo", max_retries=3, default_headers={"X-A": "1"})
- async with getattr(client, method)(directory="/elsewhere", max_retries=0, default_headers={"X-B": "2"}) as other:
- assert isinstance(other, AsyncOpencode)
- assert other._http is client._http
- assert other.max_retries == 0
- await other.agent.list()
- request = route.calls.last.request
- assert (request.headers["X-A"], request.headers["X-B"]) == ("1", "2")
- assert request.url.params["location[directory]"] == "/elsewhere"
- assert not client._http.is_closed
- assert (client.max_retries, client.directory) == (3, "/repo")
- await client.agent.list()
- await client.close()
- assert client._http.is_closed
diff --git a/uv.lock b/uv.lock
index 4310bb2..b5aef28 100644
--- a/uv.lock
+++ b/uv.lock
@@ -322,6 +322,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" },
]
+[[package]]
+name = "backports-asyncio-runner"
+version = "1.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" },
+]
+
[[package]]
name = "backrefs"
version = "8.1"
@@ -819,6 +828,24 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" },
]
+[[package]]
+name = "dirty-equals"
+version = "0.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/30/1d/c5913ac9d6615515a00f4bdc71356d302437cb74ff2e9aaccd3c14493b78/dirty_equals-0.11.tar.gz", hash = "sha256:f4ac74ee88f2d11e2fa0f65eb30ee4f07105c5f86f4dc92b09eb1138775027c3", size = 128067, upload-time = "2025-11-17T01:51:24.451Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/bb/8d/dbff05239043271dbeace563a7686212a3dd517864a35623fe4d4a64ca19/dirty_equals-0.11-py3-none-any.whl", hash = "sha256:b1d7093273fc2f9be12f443a8ead954ef6daaf6746fd42ef3a5616433ee85286", size = 28051, upload-time = "2025-11-17T01:51:22.849Z" },
+]
+
+[[package]]
+name = "distro"
+version = "1.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
+]
+
[[package]]
name = "exceptiongroup"
version = "1.3.1"
@@ -1740,13 +1767,16 @@ name = "opencode-python-sdk"
source = { editable = "." }
dependencies = [
{ name = "anyio" },
+ { name = "distro" },
{ name = "httpx" },
{ name = "pydantic" },
+ { name = "sniffio" },
{ name = "typing-extensions" },
]
[package.optional-dependencies]
aiohttp = [
+ { name = "aiohttp" },
{ name = "httpx-aiohttp" },
]
pty = [
@@ -1755,13 +1785,17 @@ pty = [
[package.dev-dependencies]
dev = [
+ { name = "dirty-equals" },
{ name = "httpx-aiohttp" },
{ name = "mypy" },
{ name = "pyright" },
{ name = "pytest" },
+ { name = "pytest-asyncio" },
{ name = "pytest-cov" },
{ name = "respx" },
{ name = "ruff" },
+ { name = "time-machine", version = "3.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
+ { name = "time-machine", version = "3.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "trio" },
{ name = "websockets" },
]
@@ -1773,24 +1807,30 @@ docs = [
[package.metadata]
requires-dist = [
+ { name = "aiohttp", marker = "extra == 'aiohttp'" },
{ name = "anyio", specifier = ">=4.4,<5" },
+ { name = "distro", specifier = ">=1.7,<2" },
{ name = "httpx", specifier = ">=0.27,<1" },
{ name = "httpx-aiohttp", marker = "extra == 'aiohttp'", specifier = ">=0.1.9,<0.3" },
{ name = "pydantic", specifier = ">=2.12,<3" },
- { name = "typing-extensions", specifier = ">=4.12" },
+ { name = "sniffio" },
+ { name = "typing-extensions", specifier = ">=4.14,<5" },
{ name = "websockets", marker = "extra == 'pty'", specifier = ">=14,<17" },
]
provides-extras = ["aiohttp", "pty"]
[package.metadata.requires-dev]
dev = [
+ { name = "dirty-equals", specifier = ">=0.6" },
{ name = "httpx-aiohttp", specifier = ">=0.1.9" },
{ name = "mypy", specifier = ">=1.18" },
{ name = "pyright", specifier = ">=1.1.408" },
{ name = "pytest", specifier = ">=8.4" },
+ { name = "pytest-asyncio", specifier = ">=1" },
{ name = "pytest-cov", specifier = ">=7" },
{ name = "respx", specifier = ">=0.22" },
{ name = "ruff", specifier = ">=0.14" },
+ { name = "time-machine", specifier = ">=2" },
{ name = "trio", specifier = ">=0.31" },
{ name = "websockets", specifier = ">=14" },
]
@@ -2316,6 +2356,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
]
+[[package]]
+name = "pytest-asyncio"
+version = "1.4.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" },
+ { name = "pytest" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f8/9a/887535e477847f5159377ab35303923644840b13c0ca03c0a467f84d4680/pytest_asyncio-1.4.1.tar.gz", hash = "sha256:e8266eb094e5ee9ffdb2068c9836f33e209ea77a970eb846933d416b5602669b", size = 145237, upload-time = "2026-10-25T21:05:37.135Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/46/d7/cd9e2708afdbf059658adaf08bdb0792ca52489871051d6ce7d498b51277/pytest_asyncio-1.4.1-py3-none-any.whl", hash = "sha256:507afa78ec77c6661164ef1a2368bc7820383fd1a14b2a7f813de8b5a92949cc", size = 17061, upload-time = "2026-10-25T21:05:35.473Z" },
+]
+
[[package]]
name = "pytest-cov"
version = "7.1.0"
@@ -2498,6 +2552,149 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" },
]
+[[package]]
+name = "time-machine"
+version = "3.5.1"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.11'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/65/d2/065a4d202d7ba093145e6f803fafd84bdcea41f3ce5f5ee6dacc77330719/time_machine-3.5.1.tar.gz", hash = "sha256:eb2c50404820fde8bfc6a0713b2a0b8eabececfecefde3a5847ae8006037829f", size = 28434, upload-time = "2026-09-08T22:19:49.989Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9e/a3/74cadbdd276da424bdbec8fcca99b5de1278ae3d74b18a38cee5591e0d81/time_machine-3.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:687ede95d69ad67eec4503cf077d56bb06e62507f769ce87d384e60d1edd3d7e", size = 27228, upload-time = "2026-09-08T22:18:37.627Z" },
+ { url = "https://files.pythonhosted.org/packages/94/16/e93bce121dba36967cd70cc83a3772897e21bd6c41e72dd25d54dfeed61b/time_machine-3.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6001f4802e0eab1d62e1a74ab7d25f64816ba77671d04e55ba75bc139f636ff1", size = 27624, upload-time = "2026-09-08T22:18:39.18Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/06/2c9427ff971f0e518c0b6359f19d1271820812a7594f03d33cf0d2dc703a/time_machine-3.5.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cf65e70122e4d6feea6a42c0ff27ade4c90d5ffaf1aaae65fc2160161d6c2b70", size = 54651, upload-time = "2026-09-08T22:18:40.201Z" },
+ { url = "https://files.pythonhosted.org/packages/56/ee/35a92d05c08c716cb13ad9818bc687b322ee7f3f05e240c67c3930ff6c8d/time_machine-3.5.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cb9cd81a98efc6dbe1fb9b0197953955369297000c9c8d09adaf0746950a498", size = 56024, upload-time = "2026-09-08T22:18:41.219Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/f7/09fe8021b7fdb7ffc7f60ff9ad3a68cb1aae3141dca9b5027c3f7b7f6dea/time_machine-3.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:080030169c275b40522e85b6a0a86a02a97e4369ae118c49682b455a0e67d802", size = 55007, upload-time = "2026-09-08T22:18:42.301Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/b9/4baa17aeb52563e73e8a5969192984958bebf5f185068863e170cbbf9a1d/time_machine-3.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:54c7f0c5afcd4f6fed8e2f83cb2e7f695231c426e7364f452976af9000608ec0", size = 54179, upload-time = "2026-09-08T22:18:43.668Z" },
+ { url = "https://files.pythonhosted.org/packages/73/2c/285f7d9a5a326150be651d46386a880e6b1f30535890760f5458e0b2c25d/time_machine-3.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:4e191c3e845c5dbbac36513932db1026a43a136dde2e18ef4bc81f419c4d81dc", size = 29456, upload-time = "2026-09-08T22:18:44.676Z" },
+ { url = "https://files.pythonhosted.org/packages/79/be/c3e1cc6970cacf46e4ba3fd263f0598ab023d4d2be33c3070102f0d2298e/time_machine-3.5.1-cp310-cp310-win_arm64.whl", hash = "sha256:877f087965da40e1858be3077d990ce26404eb1a159b438252b69fe6de897768", size = 28726, upload-time = "2026-09-08T22:18:45.723Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/07/fa50d0567f3e2e460251e19bdd36ca366fa7605271c7691025b6f09f5cc8/time_machine-3.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:619fc95eef5124da85c2d4e1e64c2cfb830264547f16c9074eefd29bce28f754", size = 27227, upload-time = "2026-09-08T22:18:46.742Z" },
+ { url = "https://files.pythonhosted.org/packages/47/00/ea7aa5da9028e8d9bd6781e09890a9855dabe65f9752dd130e6e44463868/time_machine-3.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:03ae7e486fbeda7750b4490cde8101a1b0e3f7073e9e502aeda863cbc250eb68", size = 27619, upload-time = "2026-09-08T22:18:47.801Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/a5/87fac70e43f71d1e3e22f9b796d8b5b06d250b454a7e75c2a883360580d3/time_machine-3.5.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:54bc68d0bbdd1b903c8d46cb0d42b4da7a50391dde4aa644b77e2480083a479d", size = 55496, upload-time = "2026-09-08T22:18:48.779Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/a8/a89b1fd44cc7babdd1c2c50b1748a5ce7afbae43c61b62bb8e10ab14425e/time_machine-3.5.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:811916fec2ed38c02f6bcbfdfb6d57df7dc019ded640b2eaf06ccebbcdf81599", size = 56854, upload-time = "2026-09-08T22:18:49.84Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/1f/1331f7ecbeb7bdaed40ccf7c74582c348bbccbbdab0d8a215f6d237e5c6f/time_machine-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a8d00c6a3daee89345d8f4cfb7022d81e1315bb85b2ec041a6b410ac56cb3c01", size = 55820, upload-time = "2026-09-08T22:18:50.967Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/38/1602551bf5768b9187e41fb535f54709ba6971de3ec3d5095fa11807782e/time_machine-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db35ff86b4137f16cc004e40e47e34c6f5aa0b7463a520008aabf06ffac62b75", size = 54943, upload-time = "2026-09-08T22:18:52.21Z" },
+ { url = "https://files.pythonhosted.org/packages/68/27/36f291627ecf6cf9379dc47ba8ff72c2a6f4ac8bdfe7f6e8b5ad2e56453e/time_machine-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:e9f54dc0f10093581c63d2eda7f4993c447232260b8120d8f7c196dd4c6c66af", size = 29456, upload-time = "2026-09-08T22:18:53.399Z" },
+ { url = "https://files.pythonhosted.org/packages/22/fb/4ad350fbcad15800866610ed7ab29ea2bfd8d3fa80a70e0c132f55713a32/time_machine-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:6eb740c4d6fa982bcb773c693903807ac64641c1f14a6d1adc53b9bd582ab2ff", size = 28722, upload-time = "2026-09-08T22:18:54.563Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/d3/1469cc1d412328954e7cc0d009af3a57cbb47739e29ac26c3a61f1763abd/time_machine-3.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a6415979fac70c7142cfb7d863a118ba2d8c45a96c8d6efa311c9751ec270486", size = 27665, upload-time = "2026-09-08T22:18:55.716Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/9e/ec6a281e6690cc2b8f64804a14c90c6e20397c05b05e1ee610d6810128a9/time_machine-3.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8dc65728653643b742ae5ad859d4cc50fdc456533b23c942ea4011aa99b1e67f", size = 27791, upload-time = "2026-09-08T22:18:56.71Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/8b/3ef1298a79f6352c232dc1f665eb526e4537a9fd55853845a9ef9c23618e/time_machine-3.5.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:075cc8ff3bf229d96bc7adb8b26be6b1021ee0a5213efe4f57898cda3a3bd766", size = 59941, upload-time = "2026-09-08T22:18:57.726Z" },
+ { url = "https://files.pythonhosted.org/packages/10/11/45dfb8c4f12cc877a79e68d7d46817999f8d3765a23e4afe11040c9c9d35/time_machine-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:091bd22bf9dbf297dbff35b688b7667b37a30ab7c1f5831b0688e9ddd2321386", size = 60846, upload-time = "2026-09-08T22:18:59.01Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/6c/e4d839c9eff62ece3e8ed107288c06754513389ced43df2f97cfd09807ab/time_machine-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5dbc1ffa96ff9100c617024d9119a27046f531c71839eaebd7ad8bb3542d130", size = 59646, upload-time = "2026-09-08T22:19:00.056Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/e7/5453e31a307d42d1174f476d4556d65d37a2a7d54d2244260ea022d349db/time_machine-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e9aeaee418b1696b01edc8015b33c2aa746619ca0ce6ebcbc941363ad73b8464", size = 59161, upload-time = "2026-09-08T22:19:01.355Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/b3/eac4fdcfeb225015e94ef5752e3f3fe2d1cc59ab4818935b2f6e00f5fae1/time_machine-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:1b3575d91df2325270e0ae255253e7ecb5f3add4b83d3a01b8c74e02c26470a8", size = 29616, upload-time = "2026-09-08T22:19:02.596Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/c6/1b82e057031d242dea0a592f5b341588bff158e1bac51f8a1d863c93a530/time_machine-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:991c4bc4b4a20a96355672065bafb2e517209de09b83d4ac92efe223632a713a", size = 28961, upload-time = "2026-09-08T22:19:03.602Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/aa/f2dd3acae3168f5e5076b46c42f52550d39b1b69906f77e81b486721a06a/time_machine-3.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:31aa239f2e02ec71682eadbf387d43bfe372b9409ff0dd148eca19d736402c73", size = 27663, upload-time = "2026-09-08T22:19:04.61Z" },
+ { url = "https://files.pythonhosted.org/packages/41/ce/8aa00371e2e0ced89534e84753a880989794effae19736a9ef9d59934110/time_machine-3.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cd9252e190b2c6079fd3ec9a7afc26fd26008fee1dc9940714e7d4755668b7ea", size = 27780, upload-time = "2026-09-08T22:19:05.614Z" },
+ { url = "https://files.pythonhosted.org/packages/99/fc/970e954e53e0cc3e241fc665b0f797a2708dc680553641942acf61ed6265/time_machine-3.5.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8a39af6fad7115e2c9d0deef287645260b096919d8918d52191d80ac31e43525", size = 59870, upload-time = "2026-09-08T22:19:06.624Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/e1/e1814122b0ea321e2714f369dd3de8f052eb132097757112a9fe497129cb/time_machine-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6edb56e4a41b2d717f28fbdc04ac3fc7cff43b2f573e88189d67650680eb672e", size = 60776, upload-time = "2026-09-08T22:19:08.005Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/1b/09acb019f25d918c04e470e7a410d5aeffb087b8457d6e0815c576e013ef/time_machine-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d4cea8ed128c65fe262cc216a4f46fb6080b745a3013baba188e45992ce673c5", size = 59584, upload-time = "2026-09-08T22:19:09.13Z" },
+ { url = "https://files.pythonhosted.org/packages/10/15/c4df8f02cbe773462dd60da9ab263407b4dd06b615350b889b70f6bd49b7/time_machine-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c615f45b3668fa2ccd4ad2b81899d22efe4e33d23b3540283922796de57ad37c", size = 59088, upload-time = "2026-09-08T22:19:10.587Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/e8/cae3230abdbd7fcf81bbd70c7a1047f07e98a31536db979960dbbdc2b72e/time_machine-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:c0a865aca362e645947159f2e0e3022131e591ba113b95f2b355410c36ddcd60", size = 29621, upload-time = "2026-09-08T22:19:11.688Z" },
+ { url = "https://files.pythonhosted.org/packages/20/47/224a9428327db95abe9bd52462db744fdc84db61da0cd19df2a611da3afd/time_machine-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:27095e90a2b42c2979f40146feb1bbf077dcf6a610889ae5dc36fa015e4fe2ef", size = 28956, upload-time = "2026-09-08T22:19:12.748Z" },
+ { url = "https://files.pythonhosted.org/packages/83/ef/67a4edd8f6f981be4424dc7eb086b42ff8886bdb333671d89009d57efed4/time_machine-3.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:af8f4a7d729c0d8700d826a5c6befef73010ca0a92fb19ac987d040fbca896e2", size = 27692, upload-time = "2026-09-08T22:19:13.813Z" },
+ { url = "https://files.pythonhosted.org/packages/82/b3/ec9b5758cdb3392a081d9d2da941bca39901a1709596a88695bb522f5423/time_machine-3.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2dc5d12a355e4ab2103f3527f014eb2c7fd50693f3f176cd7750c5f6f83b7e86", size = 27802, upload-time = "2026-09-08T22:19:14.942Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/a7/e0aa85084621165d659333e5b47755e592bf101a677d8a3e52501a6b3cdd/time_machine-3.5.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db80ab6d055a550d5c83f4f55d7c9918fc9531ca3f036c95db02ce266b36ac11", size = 59906, upload-time = "2026-09-08T22:19:15.966Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/d3/a2d470d512e8f1753f7fe593a76314878ac06061d189b4594f2c0f9a933d/time_machine-3.5.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0c375c0dc8a3f56a30bf044da2437ae4f869e1ba1c0ea9eb9d279e8174ec41", size = 60851, upload-time = "2026-09-08T22:19:17.017Z" },
+ { url = "https://files.pythonhosted.org/packages/92/82/a15d3c763e68578e74a0542f0bc08b22179476829bc90cde785d17566871/time_machine-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:86014c719210389bcfddebd29be3da34651866a7b516648a18f310aaf994b069", size = 59651, upload-time = "2026-09-08T22:19:18.063Z" },
+ { url = "https://files.pythonhosted.org/packages/98/44/724ab17ece00036e5260c1411889cc244888a7e5f8af19a76675f9fa6138/time_machine-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e49e9ff451a645906d621aba4fb2d22e334215230a94e0e582d67b33e24970fd", size = 59108, upload-time = "2026-09-08T22:19:19.205Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/1e/b694ab775fa2d8aa5c42fa68f02e35442531de84103ca2d1ec5c1ca86d96/time_machine-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:0f5012ac22f86366b8afd1aa01162f8ce6a7228a23a39168c7039c5cbdb9b08e", size = 29881, upload-time = "2026-09-08T22:19:20.365Z" },
+ { url = "https://files.pythonhosted.org/packages/54/fa/1d2c726ccc5492dbe73bbbbb195e34657c666bfaa8277816a0ed6c791c15/time_machine-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:3138159b26ca711991b87b4141e089ee5ce5fe7db4958612271fffd0d4209081", size = 29149, upload-time = "2026-09-08T22:19:21.369Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/59/39e94a440624a954a6084898927df5fcc047bb726be55702c91655bfcbfc/time_machine-3.5.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2250eba37ebd82fe7235f13fc863f2ad21e02aa6fe3c9d3035acb4e82f321e38", size = 28388, upload-time = "2026-09-08T22:19:22.421Z" },
+ { url = "https://files.pythonhosted.org/packages/15/fb/4bf8ee92bef263359aa9490de65a2518ed5bc785410468ab91667d6a0f39/time_machine-3.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b784ec07e978e7f504378302833ecb487b9007218fa5344c1346dd1be4904770", size = 28721, upload-time = "2026-09-08T22:19:23.511Z" },
+ { url = "https://files.pythonhosted.org/packages/21/02/49113f81a3400f23c8494c89beeea8dda73cc4a70b6a193cc0ec7bb3f111/time_machine-3.5.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b68b8f472ea34b4ad0e927777dc8aa49bfac77526571de40e358d1d5f5fa99bd", size = 71282, upload-time = "2026-09-08T22:19:24.546Z" },
+ { url = "https://files.pythonhosted.org/packages/12/32/1e34afcdec8afb135eab3304749d06e042c53974da547cb78ceaee2b2b0a/time_machine-3.5.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6b409d92cca522c0c1d0ce51894803dd2997054004c4d50273a1d748764749c", size = 73421, upload-time = "2026-09-08T22:19:25.806Z" },
+ { url = "https://files.pythonhosted.org/packages/04/95/bdaacbf58eee21eb14127702ff80fcc2e25c4938111fe948887507022f06/time_machine-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fbf8272e461ea311b9feff10021b4a735d6c0076569fb860bda49358ac8b1dee", size = 71692, upload-time = "2026-09-08T22:19:26.847Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/42/42c0796a8e1cd866fec78edb8ff1c6d360de26900f6fcdbe1dfe735acc7c/time_machine-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee142848d6f51e719d23d233ae381fb7f1db12bffee1dbd4ed7eba9e0d81ea39", size = 69830, upload-time = "2026-09-08T22:19:27.941Z" },
+ { url = "https://files.pythonhosted.org/packages/66/c9/482b603caee78c3a459f85118ef327795d41a5858914152eaffffbd64478/time_machine-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:759ec7a3d175ae3b468ec5b7e426a8d0d85f05e543e5aefa20dc99d95fd87535", size = 31108, upload-time = "2026-09-08T22:19:29.24Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/1e/b2ddad5bfbc81691eb95caa11de00132222cd761d029be86fcc81e72d4ee/time_machine-3.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:66b1c8848794ac83551c643283497fd1ed9dff19b20e86e474fc15a8032e5886", size = 29401, upload-time = "2026-09-08T22:19:30.298Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/3a/5c9a8cfc1f0bc6add00eed747196c9269a7cd2fd2d13f4f7ef3c65564f42/time_machine-3.5.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:f1baa36df51e750a9fae86f32dc8f92915ebd26dbebd4c61dda28ae46ab8faf7", size = 27693, upload-time = "2026-09-08T22:19:31.34Z" },
+ { url = "https://files.pythonhosted.org/packages/67/6a/b0f27d3831d440c91110319e7f2f733c32fa886ae6eae442d7f6e6a5bbc9/time_machine-3.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:9f1704e632dd05d93b2c350e9b317ee138071ad7ce53f38e5e06b8543d0764c0", size = 27803, upload-time = "2026-09-08T22:19:32.424Z" },
+ { url = "https://files.pythonhosted.org/packages/76/2c/337bf3a7dda4e76e0689d2e10d9c9fb34694823b1a8cc6ad15d2c935b48a/time_machine-3.5.1-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cf1b835219b61565bdc4e2bdb268b3f42a6b4443a0af4060260f65c7b3bdb781", size = 60083, upload-time = "2026-09-08T22:19:33.471Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/20/c39de4198557c112d4fdb00d14c73ceef05280fedce9ef113265ac2b5c51/time_machine-3.5.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36c1b8790ab98103184d61866feb944589957fb30f9e6e05856012787ea3aea5", size = 61045, upload-time = "2026-09-08T22:19:34.599Z" },
+ { url = "https://files.pythonhosted.org/packages/37/78/031646e6af3f36c8311888ec4c460a073c5c52d08c163df3d58c629f809e/time_machine-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:714b27fa2a2d0cde33fe363a42f3eb477078661fa0ecfae185de67e1c9348c1b", size = 59857, upload-time = "2026-09-08T22:19:35.781Z" },
+ { url = "https://files.pythonhosted.org/packages/04/87/8aba4a897e2d4bb29e786bdf6757d8ab77b2c37ba75f7e22526f619c3c5c/time_machine-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2f7315ea64cd81405ed17c4a9835d8762a28a1471dae709b5c7d8680cd5495a9", size = 59288, upload-time = "2026-09-08T22:19:36.902Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/c1/880ee7847301111a8e7bb531410e56fd5af7969a929bb174a83f0e3c0fd1/time_machine-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:a1e9423f9c03a8076d67c644c6d4dbe15f6bfc5174f928fa34a84ffb2fdbd7c6", size = 29877, upload-time = "2026-09-08T22:19:38.039Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/15/075d9cd9c56de3ef331416dbd73639ba77f9108e5dcec64e046e85c9a947/time_machine-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:73632a71eb038477a13212026f4ff26e0eb0208ee45268c345a9b97a5e102814", size = 29151, upload-time = "2026-09-08T22:19:39.325Z" },
+ { url = "https://files.pythonhosted.org/packages/05/86/b4b5a1a691f4572d5e45dd5a912f3daf0851a56af6b21814583147bd8025/time_machine-3.5.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:5b1cd9c4429c2c4e341bee940166c59c030104afa6a99ba7053c118092dd9cff", size = 28390, upload-time = "2026-09-08T22:19:40.395Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/22/6b618d2fceaf40c0963be7aa320063116abb9674cd4d8d862f01ff146a42/time_machine-3.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:63c3f74787b96066e737408d679a6a75b750e6de30c276609e99f13c0a12e271", size = 28721, upload-time = "2026-09-08T22:19:41.507Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/a3/1618a4d85a4670d073ec15fae7a44a99defd30652e8a38fd785e135d9474/time_machine-3.5.1-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2f935a9beef5e31b7cd71ac600ded551c10a748177e679bbb2858b4aa907b509", size = 71323, upload-time = "2026-09-08T22:19:42.546Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/75/d6ce2f9883240c512db3045e5ee4949f262e6d6ae22a00c2e20ee681dfbd/time_machine-3.5.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e00130b5305f3d06661a04734a7284c1445b454d22b7ff2b3bd534508fb8fcc", size = 73455, upload-time = "2026-09-08T22:19:43.607Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/cd/a098587f4766f5d4310813a8a9a3ff9cc13edf3039cd8016b2dbaed0c4a1/time_machine-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:89d4a895af01d5fcef106e09d3b966be3fcb02b41bcbf901962b8bd37d65456c", size = 71791, upload-time = "2026-09-08T22:19:44.959Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/04/783c797b2c33e10d4eb0fbf24b1ea4cffcc335d38cc931fc281b826a6e41/time_machine-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:d2f9761060f914802ed27797c3b311e992e13c5df3982c2450770d121a76803f", size = 69858, upload-time = "2026-09-08T22:19:46.143Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/bf/78ac2f56be79300491ef775535a18230845abb0dc4283f30a95daaad9644/time_machine-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:fe970adb31deac67a6f7a1dee2a7a8d0cb4c8496a0dd87c7c6e2430fc767d565", size = 31094, upload-time = "2026-09-08T22:19:47.469Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/35/86e1f95600a353361ae138268aa53cc2d17e6404801827d4ec09dc59b1af/time_machine-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:1990c1a3234d1df441ce084618b68d3c4a083f17dea4fd47adcf68d6668b507b", size = 29399, upload-time = "2026-09-08T22:19:48.68Z" },
+]
+
+[[package]]
+name = "time-machine"
+version = "3.6.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.15'",
+ "python_full_version >= '3.11' and python_full_version < '3.15'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a7/1d/48df18d3b2159ae3f49432622e3d7a23e01404cd6b99ee1361c9a4f37826/time_machine-3.6.0.tar.gz", hash = "sha256:92dfc900bbd50d239ad311473ada2201acdca3c3e14562692fd2e31489b57e24", size = 30470, upload-time = "2026-10-25T21:13:46.427Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3d/44/27ec78c871431d275bfea0375018f5432675d6462713c28dd6a49a2cb82e/time_machine-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d08cfae8b47cf680144d5befaade6d7e5e4782856b407277283ed944cbb4419d", size = 29343, upload-time = "2026-10-25T21:12:38.619Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/02/d9fe93c7d77904441704b6b5f719a843025fd9c7d64876cb47f057958b39/time_machine-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56a8352426af637b1bf73f8dd0c218470b09934b62e16d7c8ac2bc96980792c8", size = 29738, upload-time = "2026-10-25T21:12:40.011Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/5f/280d1c51dec133fe79af9187a16adcd9cf7e4552f7a2659280b54c16df29/time_machine-3.6.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:426c7cc6952af830960bda4fb1fc1ef0d735a016dc21b3fbc90fa57888e27953", size = 57612, upload-time = "2026-10-25T21:12:41.431Z" },
+ { url = "https://files.pythonhosted.org/packages/42/8e/8d27316cd683f90bab7571977c4c3c62dcf6723d3cae05392589b43e8856/time_machine-3.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eec3e790bcef4427066112f0be677f6d72c7b3f4385be44f439fdf8f110360e0", size = 58973, upload-time = "2026-10-25T21:12:42.642Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/1b/cbc6d2203826796cdaa0d97d6d4ad588e477a77d7078b1f7e0498b9b4576/time_machine-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d1f0bb9627216bfe775646c2c4059227fbc0babd7b602fb01e88bf911baf51db", size = 57939, upload-time = "2026-10-25T21:12:43.803Z" },
+ { url = "https://files.pythonhosted.org/packages/65/6c/ff82dfa19947cf6fb82e230cf537d8ae9a58a6e303270a6309f8ffdb90f1/time_machine-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:658c8864d9e2e8a94ccf3d580153a76be0cf09aeb0df5059beb74b65036ee94a", size = 57058, upload-time = "2026-10-25T21:12:45.145Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/34/7f77ac443961cebcf00bd297a19d9b7fc2f81b718332016177dad6b748b2/time_machine-3.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:4c027a938943a5f58124aac018016ffe50212d9f92be508251753764fef744fc", size = 31572, upload-time = "2026-10-25T21:12:46.292Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/ab/09e646dde49f43837e9627c2bc868df989f44eecdbcf01acf7db6bc0c11d/time_machine-3.6.0-cp311-cp311-win_arm64.whl", hash = "sha256:17ac49ce4dacc60dadb3594bd67d8cd23438fa1e4c94b7fe2544a18ff40585d0", size = 31244, upload-time = "2026-10-25T21:12:47.375Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/24/14d24f8c67faae4383f06e6dd2c988bb0ab6fd8fff7d957b8b046377f902/time_machine-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8eb28dcbbc30a1a2dd1d552063ff5ca5d47685dfebff6677095f2a924c8a4d51", size = 29779, upload-time = "2026-10-25T21:12:48.614Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/6a/78796887c6ff7037b5cb6306ade24732e02482eb1509667a70a0c7bfe459/time_machine-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f4b5585eff9dcbe87bb50dc0bd4cfccefbb89ae22268bba6cc6ee72334a0dc88", size = 29906, upload-time = "2026-10-25T21:12:49.669Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/74/63ed82fc3c12087776b4410b50a48a483dd1d31893baa721f15f9441c1df/time_machine-3.6.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8116e2a534c84743cf7125acb1602d7e5ed30f1a3121538d31320e755f819c8a", size = 62060, upload-time = "2026-10-25T21:12:51.016Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/96/f45e6de83e98beb6663a664a75de22b914df30bd934f8eeeecfe2c136079/time_machine-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fa1eae8aa0d435cd88fdc4626e67735985cec2d43ca9316e5622db82beda6a1", size = 62961, upload-time = "2026-10-25T21:12:52.087Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/87/b0cc9a330dff1fd44fa81fc8b4b946d28d888ef45ce7afb06746450ba200/time_machine-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8d1a20a35cc39654c28d725849fa3943e55b345cee6da3fd4499f625ae6f1e49", size = 61759, upload-time = "2026-10-25T21:12:53.187Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/bd/0ac3fc2a0f9e7e1af11889672147901c80fc347b4eb7b382d6be0ca75d14/time_machine-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93b2cdb5ccc53b01cac455ffe4d70f2761b75f46e514193f579c015c6a36f23f", size = 61276, upload-time = "2026-10-25T21:12:54.483Z" },
+ { url = "https://files.pythonhosted.org/packages/61/e0/1d40cb04d9900f0b79bc69747b999b9843eed294950c2c36ae044a71f114/time_machine-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:20b446b451a317e7ee326593430a8e536eec0e2bcff8082b3ae7c7d33cff8d43", size = 31733, upload-time = "2026-10-25T21:12:55.598Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/7f/47f8e6acb2764c8e40c55d1caa4426db7e2126093315a57703bb92259bad/time_machine-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:04fbc338dfa29fc0d52488fc75b058fe3c1031af9962970f193a67e99d828f90", size = 31464, upload-time = "2026-10-25T21:12:56.892Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/5a/b2faff8850f9e3b85e7403fa6344e2cf6bb208598893551043e72bdf06b1/time_machine-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:34f47752e27df515320d2fd916684044058ce7edbf3c5988fe2ec8479d7c94c5", size = 29776, upload-time = "2026-10-25T21:12:57.989Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/44/3e091ebf6c1e94f7ed4007887c33979fba47f88ad0c565881c69538bff9b/time_machine-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a7beeb38f5e35ba90eb1c8185f30d3370ed3c71c613bedcf3eee15b99366baf5", size = 29892, upload-time = "2026-10-25T21:12:59.015Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/57/0c8c5acda6fea9767590face0ebaeb69c5b47bac16e1a87936a682202ce7/time_machine-3.6.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:036d497e365a91d1a22de7604e5c3ac0dbd23c2146edc5224b6352b342da8280", size = 61988, upload-time = "2026-10-25T21:13:00.104Z" },
+ { url = "https://files.pythonhosted.org/packages/89/66/e956b37606d1bfcc023419d5ebab0bb07f2155dce02d7820f69d6d354dfc/time_machine-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7a5a57561b81114282ae422ad3ecb0db890fff3a5d6513d2a13067ebab42c9e", size = 62896, upload-time = "2026-10-25T21:13:01.155Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/15/d848bb1cb39e052969c8199f1f441ced5eb68ae58e7b3870d86ff2a99478/time_machine-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0daf90d01ca27b80087b061fcc9c1416bb743217d8da256f3403859cd8193268", size = 61700, upload-time = "2026-10-25T21:13:02.263Z" },
+ { url = "https://files.pythonhosted.org/packages/64/58/c1d622bc6defa27bb56f0935361a71142042205f30b2fac0608e66d700c1/time_machine-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a8cbf4a1f8e6d8f0171747354007568b356d99a90ee30f14c58b87ebdb82bb06", size = 61204, upload-time = "2026-10-25T21:13:03.503Z" },
+ { url = "https://files.pythonhosted.org/packages/85/af/6d7e462054eebeff23e625b6b814472d0b6987d8be2f829b1db32e9c9a2d/time_machine-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac201f2e021d6658d5179361ca08451ad1ee78121c7d9fd482d17350740e55b0", size = 31737, upload-time = "2026-10-25T21:13:04.682Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/c0/89d5a9cd009bd8bc3d6043027ccc37fee8004be38fc92fe1c99521217b76/time_machine-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:6dd9aa87aa30f4d23dbeacd7a5753efcc517f121d505098213c58bf6210d5622", size = 31465, upload-time = "2026-10-25T21:13:06.005Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/dd/0863249c8afe1f790075d4e2a403d6b078f8acd3be1d2d2f1f800495ca74/time_machine-3.6.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ee1b042d53e2dd9c007ebef442b5663ff5350496b310f660cf867d3b1b83fef8", size = 29807, upload-time = "2026-10-25T21:13:07.203Z" },
+ { url = "https://files.pythonhosted.org/packages/93/f6/8070fd21cbce27c62bf15568f76a9374d348388c20f65ddbc3e27059c600/time_machine-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5c0a522b466b396aaed15a4f812b63c9ba2fb5126b3a339464ecdd7ed4760fec", size = 29922, upload-time = "2026-10-25T21:13:08.525Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/4d/1ef056ccecb7b8656ddc8a00e5337b6d907c26026f910c722dc4e41787de/time_machine-3.6.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fc6b57968bdfea7f5cb3a88449bc8aa7ebe22414037422d8a165981be388b087", size = 62020, upload-time = "2026-10-25T21:13:09.668Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/e3/10ffc5aaa3e45073989b9df73323b40824727688646dc03ef3609999ed48/time_machine-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73804ab0191b808f3e5e8d35a937583c8d9becd2fe6fd12684464cf72a4317fe", size = 62969, upload-time = "2026-10-25T21:13:10.821Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/ee/71faddd2ca47b2642f6e90e59a1276e8ba4b3a064fdac6ba9696f7e72760/time_machine-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:667b4bd284ac7d0403e074bcb22f0725304e41ce2833ef653a252269e691b556", size = 61823, upload-time = "2026-10-25T21:13:12.013Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/26/c78a262c0bfc0cd48cc379045f9b28288fa8505bbae0fce89add7c849265/time_machine-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f7964b7e7c606ded4a5cebffddc210f047a89c053d5125477a2fdcd31c79f975", size = 61231, upload-time = "2026-10-25T21:13:13.159Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/a7/a958799052c879a06a35e8c3b4a014ee848623e7fae1b6ff6ca093202508/time_machine-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:b12af83658aee27c55e7a5ef86a4b5e7cbb398987767ce40cf3140496b88d24c", size = 32019, upload-time = "2026-10-25T21:13:14.921Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/94/a386ab8c7cf70399b01297e86e479d5c98a19efc0c4a213cb1e4104f3e08/time_machine-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:2532a5be0881b3ef1ec0a5ff7506a361aebc740ac866b44b8ef21067f7d82d93", size = 31685, upload-time = "2026-10-25T21:13:15.999Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/51/5d1f3b581535e0bfabeb7567b38d4fe5d236f21de9a8874353eaf1470af6/time_machine-3.6.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dd4daffe3af67ab46f50ac8bf9b81a2f6e8fed44a0968b2910e0df14581d8b5f", size = 30505, upload-time = "2026-10-25T21:13:17.323Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/c5/7bdcd64d02b8688385cc7fb45608caa46a045e986359965e851198f3d8dc/time_machine-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b3a22eebbfeff1c5ea2e2fe2f8fb9b46818c8a4dcf1f5f699fe4b6a8d8958611", size = 30830, upload-time = "2026-10-25T21:13:18.67Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/bb/94b8f550797d5eab3cf1162312eb633cc8c5de4528c8b75b3a5b2bc124a1/time_machine-3.6.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f23f7515014c77ae1ce7a686a832ad5b5106bda6dc71f92a1c8af01f9cc3d2d5", size = 73398, upload-time = "2026-10-25T21:13:19.953Z" },
+ { url = "https://files.pythonhosted.org/packages/30/a1/4ce2a40033b237b1f034f2b69e4bfd198b54d84bf369309a2926bb251da7/time_machine-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b08ba7399fe889157fc9ff37b09327c1cc2b6dc430a30281e500ce7ef068e1c1", size = 75504, upload-time = "2026-10-25T21:13:21.133Z" },
+ { url = "https://files.pythonhosted.org/packages/af/30/c84e62e282503181d36491a2f590e53831843046654acf09369b8ac28b93/time_machine-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a6210123b554125ab17b2e23afd7c9b3862547a0ef2bfbafc1a2ecba4a139936", size = 73866, upload-time = "2026-10-25T21:13:22.549Z" },
+ { url = "https://files.pythonhosted.org/packages/de/cb/0fedaef62401d21d64c6f7849821e30037c114fafe584880ed2e2c60bcb4/time_machine-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a5dbf1d2e404a74b294de66f4e8df0339a5bfae5681d8429555a1e955b022a5e", size = 71952, upload-time = "2026-10-25T21:13:23.661Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/b4/f8d00493a71527e001b0b6002ccadbaedf378682a94c8932d36205702b26/time_machine-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:25b864f5f0a2ee778e8800926a426403e4bcf5f0a2020ccc969e7bb9d8880e20", size = 33246, upload-time = "2026-10-25T21:13:24.806Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/82/034d83db7a9226c2a2a6dc4fceefad0938ea0142aef56e8c67aeb877b958/time_machine-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:97a5d129078edc89b53b6aa48ce321065090d65342c2393fd76dc8c7acf95d3d", size = 31988, upload-time = "2026-10-25T21:13:25.907Z" },
+ { url = "https://files.pythonhosted.org/packages/da/22/ed5acafe5392687e3787d9e59b9c1db5e5b34bd5064d53b07b56c2740cee/time_machine-3.6.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:aa74f3064d631f26687a9b9996a13177e3aaf8c253558ea077e7ef716a391763", size = 29808, upload-time = "2026-10-25T21:13:27Z" },
+ { url = "https://files.pythonhosted.org/packages/04/eb/82958560cbd9b076a137e331858049afffda8a9650b417c2a39052e74f99/time_machine-3.6.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:25b75baa8c7a2d855caa55ea7936b41cef328124232115c9de0d3eeb9119cfed", size = 29915, upload-time = "2026-10-25T21:13:28.094Z" },
+ { url = "https://files.pythonhosted.org/packages/32/b1/b6796da5d40e21d5d52726988abb85631cf5c24908842514fc554df101a0/time_machine-3.6.0-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9685e6ca5a3eadd26e6a5a89aa2fd4e5d2c0d432cc7db90ec97dca56586860f6", size = 62196, upload-time = "2026-10-25T21:13:29.435Z" },
+ { url = "https://files.pythonhosted.org/packages/45/9c/d1682fb335d53606ddc4b09462ee59f6e465ca61551b84d53be0b4bb3620/time_machine-3.6.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc880904436c384d2c1b44c483682b33fb05804c4c775645ff4508604f2e6c47", size = 63155, upload-time = "2026-10-25T21:13:30.78Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/0e/e13d6c7520ae0fb100c18ab2f6594d4016c0390036104bab71a2a0a0a2a0/time_machine-3.6.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:e4188dad6f34ee9db37702678890ccd36708230a133d0ea02be4e902da9913d8", size = 61966, upload-time = "2026-10-25T21:13:32.124Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/b2/5134fc5e16aed1913c41f41337df33d44825af96690d4dba4408fad19656/time_machine-3.6.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:27eb27c0d1dc8a8547fe216aae8a6b7b223f8a620c78fdf746d81fea10a19d16", size = 61403, upload-time = "2026-10-25T21:13:33.263Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/30/e65d9e3b71b727c4b15e3bbaf7a1ce33cc46e4a9fbc3c6b4e8eac57df6eb/time_machine-3.6.0-cp315-cp315-win_amd64.whl", hash = "sha256:bc5588b7bce73f519e434fb7107150ee2df8d3b1090c249ddef0adf7b19f63ff", size = 32021, upload-time = "2026-10-25T21:13:34.362Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/4d/439cff6b263ea3c38d645ae97c99e958a60f5ea38da131fa7f91859eb0e2/time_machine-3.6.0-cp315-cp315-win_arm64.whl", hash = "sha256:d574dd2a184c3ffeb21b9d917e937a89bb55db05fa049fcf0006ddfa59b17434", size = 31686, upload-time = "2026-10-25T21:13:35.455Z" },
+ { url = "https://files.pythonhosted.org/packages/68/56/1a1e92a95f1eeee320a446cba62b88d4b7814d06f815ae4fdb1434ec103d/time_machine-3.6.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:42b6851d89cf3a3bf25d914751b25b0f5cf1f163dfa5a3008e81986727ee1c18", size = 30503, upload-time = "2026-10-25T21:13:36.469Z" },
+ { url = "https://files.pythonhosted.org/packages/59/00/35f6889ce3470f8664eba17236ca660614c33e09f164a39484f9a4cc8324/time_machine-3.6.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:6b5bedbe96f6af9a1b66547ab077575eccde601f1f8aa9e67497172d82c58d05", size = 30835, upload-time = "2026-10-25T21:13:37.535Z" },
+ { url = "https://files.pythonhosted.org/packages/19/26/ce9815060e53438a7d00b5bd557c807300f784baafebc6b615f69de0a55c/time_machine-3.6.0-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e80d73bd296ba4f83b53670e51dfaf7483d4afa34d0c67aa4c0d1ecceac4e0fd", size = 73435, upload-time = "2026-10-25T21:13:38.578Z" },
+ { url = "https://files.pythonhosted.org/packages/31/db/86c7c895be3c1eb0b7d4fa46cf1673639db90b92ae172388735dcd0f22e2/time_machine-3.6.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54796a60776155c1004cc45dcde34130cf2c290caf1f93b6a731bebd2ddd3e72", size = 75566, upload-time = "2026-10-25T21:13:39.69Z" },
+ { url = "https://files.pythonhosted.org/packages/21/ca/80fc09e35863317be4f1833f4e66a7b24dfdeb4f46e3d7c5c11ee2ab0df7/time_machine-3.6.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:ef0c372693e84eae26b3b51f7e16e8cadaddad010c4feb6f9f5b7aaa59c14fc9", size = 73900, upload-time = "2026-10-25T21:13:40.808Z" },
+ { url = "https://files.pythonhosted.org/packages/87/fa/f4d1c73f0330a6b370866b2c57ce8f4fac45cb51bd6003bf602713efc4ed/time_machine-3.6.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7986186773d8e225b0d950729d8014179368e966277a20fe3c0522eb2bfec133", size = 71964, upload-time = "2026-10-25T21:13:41.889Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/85/f5e3fc23295bca132382333782c4b5d155fb1778b25dcc76422cd4c4b3c4/time_machine-3.6.0-cp315-cp315t-win_amd64.whl", hash = "sha256:45a80b0a209572573d1c6d6a6020ceaea9b353f6c579a89d07be92977e3250c6", size = 33244, upload-time = "2026-10-25T21:13:42.954Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/05/584e838ba8a421aeb72dd2ed5c62548b9a98ef52ad53192c0bdc1c1efe65/time_machine-3.6.0-cp315-cp315t-win_arm64.whl", hash = "sha256:6962f1b7bb0ca7c1676bec287a6e23f3bf51d0eaf8df6fad3994e77df7fcf59e", size = 31985, upload-time = "2026-10-25T21:13:45.304Z" },
+]
+
[[package]]
name = "tinycss2"
version = "1.5.1"
From f075ef2133b8f61a3a12531bfa99c3e582c83023 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 16:02:01 +0530
Subject: [PATCH 03/41] Keep the SDK's feature tests in tests/custom, drop
tests of the replaced runtime
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
tests/custom/__init__.py | 0
tests/custom/conftest.py | 45 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 45 insertions(+)
create mode 100644 tests/custom/__init__.py
create mode 100644 tests/custom/conftest.py
diff --git a/tests/custom/__init__.py b/tests/custom/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/custom/conftest.py b/tests/custom/conftest.py
new file mode 100644
index 0000000..ee9edc9
--- /dev/null
+++ b/tests/custom/conftest.py
@@ -0,0 +1,45 @@
+from __future__ import annotations
+
+from collections.abc import AsyncIterator, Iterator
+
+import pytest
+import respx
+
+from opencode import AsyncOpencode, Opencode
+
+BASE_URL = "http://opencode.test"
+PASSWORD = "test-password"
+
+
+@pytest.fixture
+def anyio_backend() -> str:
+ return "asyncio"
+
+
+@pytest.fixture
+def mock_api() -> Iterator[respx.MockRouter]:
+ with respx.mock(base_url=BASE_URL, assert_all_called=False) as router:
+ yield router
+
+
+@pytest.fixture
+def client(mock_api: respx.MockRouter) -> Iterator[Opencode]:
+ with Opencode(BASE_URL, password=PASSWORD, max_retries=2) as instance:
+ yield instance
+
+
+@pytest.fixture
+async def async_client(mock_api: respx.MockRouter) -> AsyncIterator[AsyncOpencode]:
+ async with AsyncOpencode(BASE_URL, password=PASSWORD, max_retries=2) as instance:
+ yield instance
+
+
+@pytest.fixture(autouse=True)
+def _no_retry_sleep(monkeypatch: pytest.MonkeyPatch) -> None:
+ """Retries back off with real sleeps; skip them so tests stay fast."""
+
+ async def instant(_: float) -> None:
+ return None
+
+ monkeypatch.setattr("opencode._base_client.time.sleep", lambda _: None)
+ monkeypatch.setattr("opencode._base_client.anyio.sleep", instant)
From b3e0e3c22697d71ce5c78b4099c3e4a688a417cb Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 16:02:01 +0530
Subject: [PATCH 04/41] Move the custom modules onto Ferrule's runtime: typed
errors and shared helpers
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
src/opencode/_errors.py | 402 +++++++++--------------------------
src/opencode/_pty.py | 2 +-
src/opencode/_rpc.py | 7 +-
src/opencode/_server.py | 4 +-
src/opencode/_service.py | 2 +-
src/opencode/lib/_ready.py | 3 +-
src/opencode/lib/_support.py | 130 +++++++++++
7 files changed, 246 insertions(+), 304 deletions(-)
create mode 100644 src/opencode/lib/_support.py
diff --git a/src/opencode/_errors.py b/src/opencode/_errors.py
index 73efad7..40ef782 100644
--- a/src/opencode/_errors.py
+++ b/src/opencode/_errors.py
@@ -1,63 +1,38 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
+"""Errors the OpenCode server declares, keyed by the ``_tag`` of the error body.
-"""Errors the OpenCode server declares, keyed by their ``_tag``."""
+Custom code (kept by Ferrule's regeneration): the generated client's ``_make_status_error`` asks
+:func:`tagged_status_error` first, so a response whose body carries a known ``_tag`` raises its
+own class, a subclass of the status class Ferrule's runtime would raise (``SessionNotFoundError``
+is a ``NotFoundError``). ``error`` gives the body as an :class:`ErrorBody`.
+"""
from __future__ import annotations
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Any, ClassVar, Optional, cast
+
+import pydantic
from ._exceptions import (
APIStatusError,
AuthenticationError,
BadRequestError,
ConflictError,
- ContentTooLargeError,
InternalServerError,
NotFoundError,
- NotImplementedServerError,
PermissionDeniedError,
- ServiceUnavailableError,
)
+from ._models import BaseModel
if TYPE_CHECKING:
- from .types import (
- AgentNotFoundErrorBody,
- CommandExecutionErrorBody,
- CommandNotFoundErrorBody,
- FileNotFoundErrorBody,
- ForbiddenErrorBody,
- FormAlreadySettledErrorBody,
- FormInvalidAnswerErrorBody,
- FormNotFoundErrorBody,
- InstructionEntryValueTooLargeErrorBody,
- IntegrationAttemptNotFoundErrorBody,
- IntegrationMethodNotFoundErrorBody,
- IntegrationNotFoundErrorBody,
- InvalidCursorErrorBody,
- InvalidRequestErrorBody,
- LocationNotFoundErrorBody,
- McpServerNotFoundErrorBody,
- MessageNotFoundErrorBody,
- PermissionNotFoundErrorBody,
- PermissionSavedNotFoundErrorBody,
- ProjectNotFoundErrorBody,
- ProviderNotFoundErrorBody,
- PtyNotFoundErrorBody,
- RpcErrorBody,
- RpcInternalErrorBody,
- SessionBusyErrorBody,
- SessionNotFoundErrorBody,
- ShellNotFoundErrorBody,
- SkillNotFoundErrorBody,
- UnauthorizedErrorBody,
- UnknownErrorBody,
- VcsInitNotSupportedErrorBody,
- WorktreeErrorBody,
- )
+ import httpx
__all__ = [
"TAGGED_ERRORS",
+ "ContentTooLargeError",
+ "ErrorBody",
+ "NotImplementedServerError",
+ "ServiceUnavailableError",
+ "TaggedError",
"AgentNotFoundError",
"CommandExecutionError",
"CommandNotFoundError",
@@ -90,424 +65,245 @@
"UnknownError",
"VcsInitNotSupportedError",
"WorktreeError",
+ "tagged_status_error",
]
-class AgentNotFoundError(NotFoundError):
- """The server answered 404 with ``_tag`` ``AgentNotFoundError``."""
+class ErrorBody(BaseModel):
+ """The JSON body of an OpenCode error. Fields other than ``_tag`` vary by error and stay as sent."""
- tag = "AgentNotFoundError"
+ tag: Optional[str] = pydantic.Field(default=None, alias="_tag")
- @property
- def error(self) -> AgentNotFoundErrorBody:
- """The error body as a typed model."""
- from .types import AgentNotFoundErrorBody
+ message: Optional[str] = None
- return self._typed_body(AgentNotFoundErrorBody)
+class ContentTooLargeError(APIStatusError):
+ """The request body is too large (413)."""
-class CommandExecutionError(InternalServerError):
- """The server answered 500 with ``_tag`` ``CommandExecutionError``."""
- tag = "CommandExecutionError"
+class NotImplementedServerError(InternalServerError):
+ """The server does not implement this (501)."""
- @property
- def error(self) -> CommandExecutionErrorBody:
- """The error body as a typed model."""
- from .types import CommandExecutionErrorBody
- return self._typed_body(CommandExecutionErrorBody)
+class ServiceUnavailableError(InternalServerError):
+ """The server is not ready or is overloaded (503)."""
-class CommandNotFoundError(NotFoundError):
- """The server answered 404 with ``_tag`` ``CommandNotFoundError``."""
+class TaggedError(APIStatusError):
+ """A server error with a ``_tag`` this client knows."""
- tag = "CommandNotFoundError"
+ tag: ClassVar[str | None] = None
+ """The server ``_tag`` this class represents."""
@property
- def error(self) -> CommandNotFoundErrorBody:
- """The error body as a typed model."""
- from .types import CommandNotFoundErrorBody
+ def error_tag(self) -> str | None:
+ """The ``_tag`` the server sent."""
+ body = self.body
+ value: object = cast("dict[str, Any]", body).get("_tag") if isinstance(body, dict) else None
+ return value if isinstance(value, str) else None
- return self._typed_body(CommandNotFoundErrorBody)
+ @property
+ def error(self) -> ErrorBody:
+ """The error body as a model; it is built as sent when it does not validate."""
+ raw: object = self.body
+ body: dict[str, Any] = cast("dict[str, Any]", raw) if isinstance(raw, dict) else {}
+ try:
+ return ErrorBody.model_validate(body)
+ except pydantic.ValidationError:
+ return ErrorBody.model_construct(**body)
-class RemoteFileNotFoundError(NotFoundError):
- """The server answered 404 with ``_tag`` ``FileNotFoundError``."""
+class AgentNotFoundError(TaggedError, NotFoundError):
+ """The server answered 404 with ``_tag`` ``AgentNotFoundError``."""
- tag = "FileNotFoundError"
+ tag = "AgentNotFoundError"
- @property
- def error(self) -> FileNotFoundErrorBody:
- """The error body as a typed model."""
- from .types import FileNotFoundErrorBody
- return self._typed_body(FileNotFoundErrorBody)
+class CommandExecutionError(TaggedError, InternalServerError):
+ """The server answered 500 with ``_tag`` ``CommandExecutionError``."""
+ tag = "CommandExecutionError"
-class ForbiddenError(PermissionDeniedError):
- """The server answered 403 with ``_tag`` ``ForbiddenError``."""
- tag = "ForbiddenError"
+class CommandNotFoundError(TaggedError, NotFoundError):
+ """The server answered 404 with ``_tag`` ``CommandNotFoundError``."""
- @property
- def error(self) -> ForbiddenErrorBody:
- """The error body as a typed model."""
- from .types import ForbiddenErrorBody
+ tag = "CommandNotFoundError"
- return self._typed_body(ForbiddenErrorBody)
+class RemoteFileNotFoundError(TaggedError, NotFoundError):
+ """The server answered 404 with ``_tag`` ``FileNotFoundError``."""
-class FormAlreadySettledError(ConflictError):
- """The server answered 409 with ``_tag`` ``FormAlreadySettledError``."""
+ tag = "FileNotFoundError"
- tag = "FormAlreadySettledError"
- @property
- def error(self) -> FormAlreadySettledErrorBody:
- """The error body as a typed model."""
- from .types import FormAlreadySettledErrorBody
+class ForbiddenError(TaggedError, PermissionDeniedError):
+ """The server answered 403 with ``_tag`` ``ForbiddenError``."""
+
+ tag = "ForbiddenError"
- return self._typed_body(FormAlreadySettledErrorBody)
+class FormAlreadySettledError(TaggedError, ConflictError):
+ """The server answered 409 with ``_tag`` ``FormAlreadySettledError``."""
-class FormInvalidAnswerError(BadRequestError):
- """The server answered 400 with ``_tag`` ``FormInvalidAnswerError``."""
+ tag = "FormAlreadySettledError"
- tag = "FormInvalidAnswerError"
- @property
- def error(self) -> FormInvalidAnswerErrorBody:
- """The error body as a typed model."""
- from .types import FormInvalidAnswerErrorBody
+class FormInvalidAnswerError(TaggedError, BadRequestError):
+ """The server answered 400 with ``_tag`` ``FormInvalidAnswerError``."""
- return self._typed_body(FormInvalidAnswerErrorBody)
+ tag = "FormInvalidAnswerError"
-class FormNotFoundError(NotFoundError):
+class FormNotFoundError(TaggedError, NotFoundError):
"""The server answered 404 with ``_tag`` ``FormNotFoundError``."""
tag = "FormNotFoundError"
- @property
- def error(self) -> FormNotFoundErrorBody:
- """The error body as a typed model."""
- from .types import FormNotFoundErrorBody
-
- return self._typed_body(FormNotFoundErrorBody)
-
-class InstructionEntryValueTooLargeError(ContentTooLargeError):
+class InstructionEntryValueTooLargeError(TaggedError, ContentTooLargeError):
"""The server answered 413 with ``_tag`` ``InstructionEntryValueTooLargeError``."""
tag = "InstructionEntryValueTooLargeError"
- @property
- def error(self) -> InstructionEntryValueTooLargeErrorBody:
- """The error body as a typed model."""
- from .types import InstructionEntryValueTooLargeErrorBody
- return self._typed_body(InstructionEntryValueTooLargeErrorBody)
-
-
-class IntegrationAttemptNotFoundError(NotFoundError):
+class IntegrationAttemptNotFoundError(TaggedError, NotFoundError):
"""The server answered 404 with ``_tag`` ``IntegrationAttemptNotFoundError``."""
tag = "IntegrationAttemptNotFoundError"
- @property
- def error(self) -> IntegrationAttemptNotFoundErrorBody:
- """The error body as a typed model."""
- from .types import IntegrationAttemptNotFoundErrorBody
- return self._typed_body(IntegrationAttemptNotFoundErrorBody)
-
-
-class IntegrationMethodNotFoundError(NotFoundError):
+class IntegrationMethodNotFoundError(TaggedError, NotFoundError):
"""The server answered 404 with ``_tag`` ``IntegrationMethodNotFoundError``."""
tag = "IntegrationMethodNotFoundError"
- @property
- def error(self) -> IntegrationMethodNotFoundErrorBody:
- """The error body as a typed model."""
- from .types import IntegrationMethodNotFoundErrorBody
-
- return self._typed_body(IntegrationMethodNotFoundErrorBody)
-
-class IntegrationNotFoundError(NotFoundError):
+class IntegrationNotFoundError(TaggedError, NotFoundError):
"""The server answered 404 with ``_tag`` ``IntegrationNotFoundError``."""
tag = "IntegrationNotFoundError"
- @property
- def error(self) -> IntegrationNotFoundErrorBody:
- """The error body as a typed model."""
- from .types import IntegrationNotFoundErrorBody
-
- return self._typed_body(IntegrationNotFoundErrorBody)
-
-class InvalidCursorError(BadRequestError):
+class InvalidCursorError(TaggedError, BadRequestError):
"""The server answered 400 with ``_tag`` ``InvalidCursorError``."""
tag = "InvalidCursorError"
- @property
- def error(self) -> InvalidCursorErrorBody:
- """The error body as a typed model."""
- from .types import InvalidCursorErrorBody
-
- return self._typed_body(InvalidCursorErrorBody)
-
-class InvalidRequestError(BadRequestError):
+class InvalidRequestError(TaggedError, BadRequestError):
"""The server answered 400 with ``_tag`` ``InvalidRequestError``."""
tag = "InvalidRequestError"
- @property
- def error(self) -> InvalidRequestErrorBody:
- """The error body as a typed model."""
- from .types import InvalidRequestErrorBody
-
- return self._typed_body(InvalidRequestErrorBody)
-
-class LocationNotFoundError(NotFoundError):
+class LocationNotFoundError(TaggedError, NotFoundError):
"""The server answered 404 with ``_tag`` ``LocationNotFoundError``."""
tag = "LocationNotFoundError"
- @property
- def error(self) -> LocationNotFoundErrorBody:
- """The error body as a typed model."""
- from .types import LocationNotFoundErrorBody
-
- return self._typed_body(LocationNotFoundErrorBody)
-
-class McpServerNotFoundError(NotFoundError):
+class McpServerNotFoundError(TaggedError, NotFoundError):
"""The server answered 404 with ``_tag`` ``McpServerNotFoundError``."""
tag = "McpServerNotFoundError"
- @property
- def error(self) -> McpServerNotFoundErrorBody:
- """The error body as a typed model."""
- from .types import McpServerNotFoundErrorBody
-
- return self._typed_body(McpServerNotFoundErrorBody)
-
-class MessageNotFoundError(NotFoundError):
+class MessageNotFoundError(TaggedError, NotFoundError):
"""The server answered 404 with ``_tag`` ``MessageNotFoundError``."""
tag = "MessageNotFoundError"
- @property
- def error(self) -> MessageNotFoundErrorBody:
- """The error body as a typed model."""
- from .types import MessageNotFoundErrorBody
-
- return self._typed_body(MessageNotFoundErrorBody)
-
-class PermissionNotFoundError(NotFoundError):
+class PermissionNotFoundError(TaggedError, NotFoundError):
"""The server answered 404 with ``_tag`` ``PermissionNotFoundError``."""
tag = "PermissionNotFoundError"
- @property
- def error(self) -> PermissionNotFoundErrorBody:
- """The error body as a typed model."""
- from .types import PermissionNotFoundErrorBody
-
- return self._typed_body(PermissionNotFoundErrorBody)
-
-class PermissionSavedNotFoundError(NotFoundError):
+class PermissionSavedNotFoundError(TaggedError, NotFoundError):
"""The server answered 404 with ``_tag`` ``PermissionSavedNotFoundError``."""
tag = "PermissionSavedNotFoundError"
- @property
- def error(self) -> PermissionSavedNotFoundErrorBody:
- """The error body as a typed model."""
- from .types import PermissionSavedNotFoundErrorBody
-
- return self._typed_body(PermissionSavedNotFoundErrorBody)
-
-class ProjectNotFoundError(NotFoundError):
+class ProjectNotFoundError(TaggedError, NotFoundError):
"""The server answered 404 with ``_tag`` ``ProjectNotFoundError``."""
tag = "ProjectNotFoundError"
- @property
- def error(self) -> ProjectNotFoundErrorBody:
- """The error body as a typed model."""
- from .types import ProjectNotFoundErrorBody
-
- return self._typed_body(ProjectNotFoundErrorBody)
-
-class ProviderNotFoundError(NotFoundError):
+class ProviderNotFoundError(TaggedError, NotFoundError):
"""The server answered 404 with ``_tag`` ``ProviderNotFoundError``."""
tag = "ProviderNotFoundError"
- @property
- def error(self) -> ProviderNotFoundErrorBody:
- """The error body as a typed model."""
- from .types import ProviderNotFoundErrorBody
- return self._typed_body(ProviderNotFoundErrorBody)
-
-
-class PtyNotFoundError(NotFoundError):
+class PtyNotFoundError(TaggedError, NotFoundError):
"""The server answered 404 with ``_tag`` ``PtyNotFoundError``."""
tag = "PtyNotFoundError"
- @property
- def error(self) -> PtyNotFoundErrorBody:
- """The error body as a typed model."""
- from .types import PtyNotFoundErrorBody
-
- return self._typed_body(PtyNotFoundErrorBody)
-
-class RpcError(BadRequestError):
+class RpcError(TaggedError, BadRequestError):
"""The server answered 400 with ``_tag`` ``RpcError``."""
tag = "RpcError"
- @property
- def error(self) -> RpcErrorBody:
- """The error body as a typed model."""
- from .types import RpcErrorBody
-
- return self._typed_body(RpcErrorBody)
-
-class RpcInternalError(InternalServerError):
+class RpcInternalError(TaggedError, InternalServerError):
"""The server answered 500 with ``_tag`` ``RpcInternalError``."""
tag = "RpcInternalError"
- @property
- def error(self) -> RpcInternalErrorBody:
- """The error body as a typed model."""
- from .types import RpcInternalErrorBody
-
- return self._typed_body(RpcInternalErrorBody)
-
-class SessionBusyError(ConflictError):
+class SessionBusyError(TaggedError, ConflictError):
"""The server answered 409 with ``_tag`` ``SessionBusyError``."""
tag = "SessionBusyError"
- @property
- def error(self) -> SessionBusyErrorBody:
- """The error body as a typed model."""
- from .types import SessionBusyErrorBody
-
- return self._typed_body(SessionBusyErrorBody)
-
-class SessionNotFoundError(NotFoundError):
+class SessionNotFoundError(TaggedError, NotFoundError):
"""The server answered 404 with ``_tag`` ``SessionNotFoundError``."""
tag = "SessionNotFoundError"
- @property
- def error(self) -> SessionNotFoundErrorBody:
- """The error body as a typed model."""
- from .types import SessionNotFoundErrorBody
-
- return self._typed_body(SessionNotFoundErrorBody)
-
-class ShellNotFoundError(NotFoundError):
+class ShellNotFoundError(TaggedError, NotFoundError):
"""The server answered 404 with ``_tag`` ``ShellNotFoundError``."""
tag = "ShellNotFoundError"
- @property
- def error(self) -> ShellNotFoundErrorBody:
- """The error body as a typed model."""
- from .types import ShellNotFoundErrorBody
- return self._typed_body(ShellNotFoundErrorBody)
-
-
-class SkillNotFoundError(NotFoundError):
+class SkillNotFoundError(TaggedError, NotFoundError):
"""The server answered 404 with ``_tag`` ``SkillNotFoundError``."""
tag = "SkillNotFoundError"
- @property
- def error(self) -> SkillNotFoundErrorBody:
- """The error body as a typed model."""
- from .types import SkillNotFoundErrorBody
-
- return self._typed_body(SkillNotFoundErrorBody)
-
-class UnauthorizedError(AuthenticationError):
+class UnauthorizedError(TaggedError, AuthenticationError):
"""The server answered 401 with ``_tag`` ``UnauthorizedError``."""
tag = "UnauthorizedError"
- @property
- def error(self) -> UnauthorizedErrorBody:
- """The error body as a typed model."""
- from .types import UnauthorizedErrorBody
-
- return self._typed_body(UnauthorizedErrorBody)
-
-class UnknownError(InternalServerError):
+class UnknownError(TaggedError, InternalServerError):
"""The server answered 500 with ``_tag`` ``UnknownError``."""
tag = "UnknownError"
- @property
- def error(self) -> UnknownErrorBody:
- """The error body as a typed model."""
- from .types import UnknownErrorBody
-
- return self._typed_body(UnknownErrorBody)
-
-class VcsInitNotSupportedError(NotImplementedServerError):
+class VcsInitNotSupportedError(TaggedError, NotImplementedServerError):
"""The server answered 501 with ``_tag`` ``VcsInitNotSupportedError``."""
tag = "VcsInitNotSupportedError"
- @property
- def error(self) -> VcsInitNotSupportedErrorBody:
- """The error body as a typed model."""
- from .types import VcsInitNotSupportedErrorBody
-
- return self._typed_body(VcsInitNotSupportedErrorBody)
-
-class WorktreeError(BadRequestError):
+class WorktreeError(TaggedError, BadRequestError):
"""The server answered 400 with ``_tag`` ``WorktreeError``."""
tag = "WorktreeError"
- @property
- def error(self) -> WorktreeErrorBody:
- """The error body as a typed model."""
- from .types import WorktreeErrorBody
-
- return self._typed_body(WorktreeErrorBody)
-
TAGGED_ERRORS: dict[str, type[APIStatusError]] = {
"AgentNotFoundError": AgentNotFoundError,
@@ -545,3 +341,17 @@ def error(self) -> WorktreeErrorBody:
"VcsInitNotSupportedError": VcsInitNotSupportedError,
"WorktreeError": WorktreeError,
}
+
+_BY_STATUS: dict[int, type[APIStatusError]] = {
+ 413: ContentTooLargeError,
+ 501: NotImplementedServerError,
+ 503: ServiceUnavailableError,
+}
+
+
+def tagged_status_error(err_msg: str, *, body: object, response: httpx.Response) -> APIStatusError | None:
+ """The error for a ``_tag`` this client knows, or for 413, 501 and 503; ``None`` otherwise."""
+ tag: object = cast("dict[str, Any]", body).get("_tag") if isinstance(body, dict) else None
+ cls = TAGGED_ERRORS.get(tag) if isinstance(tag, str) else None
+ cls = cls or _BY_STATUS.get(response.status_code)
+ return None if cls is None else cls(err_msg, response=response, body=body)
diff --git a/src/opencode/_pty.py b/src/opencode/_pty.py
index 3054d32..938f475 100644
--- a/src/opencode/_pty.py
+++ b/src/opencode/_pty.py
@@ -34,9 +34,9 @@
from typing_extensions import Self
from ._exceptions import OpencodeError
-from ._qs import encode_path_segment, encode_query
from ._resource import AsyncAPIResource, SyncAPIResource
from ._types import NOT_GIVEN, NotGiven
+from .lib._support import encode_path_segment, encode_query
if TYPE_CHECKING:
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator, Mapping
diff --git a/src/opencode/_rpc.py b/src/opencode/_rpc.py
index 22229fa..96248ef 100644
--- a/src/opencode/_rpc.py
+++ b/src/opencode/_rpc.py
@@ -39,10 +39,10 @@ class Counter(RpcDefinition, id="counter"):
import pydantic
from typing_extensions import Self
-from ._base_client import type_adapter
from ._errors import RpcError, RpcInternalError
-from ._exceptions import OpencodeError, validation_summary
+from ._exceptions import OpencodeError
from ._types import NOT_GIVEN, NotGiven
+from .lib._support import type_adapter, validation_summary
from .resources.rpc import AsyncRpcResource, RpcResource
if TYPE_CHECKING:
@@ -51,7 +51,8 @@ class Counter(RpcDefinition, id="counter"):
from ._client import AsyncOpencode, Opencode
from ._events import AsyncSubscription, Subscription
- from ._types import Headers, Timeout
+ from ._types import Headers
+ from .lib._support import Timeout
from .types import Location, RpcEvent
__all__ = [
diff --git a/src/opencode/_server.py b/src/opencode/_server.py
index 4aae6da..63d4bc2 100644
--- a/src/opencode/_server.py
+++ b/src/opencode/_server.py
@@ -21,14 +21,14 @@
from typing_extensions import Self
from ._client import AsyncOpencode, Opencode
-from ._constants import DEFAULT_USERNAME
from ._exceptions import OpencodeError
+from .lib._support import DEFAULT_USERNAME
if TYPE_CHECKING:
from collections.abc import Mapping
from types import TracebackType
- from ._types import Timeout
+ from .lib._support import Timeout
__all__ = ["AsyncOpencodeServer", "OpencodeServer", "ServerStartError"]
diff --git a/src/opencode/_service.py b/src/opencode/_service.py
index 763d4bb..abfdd68 100644
--- a/src/opencode/_service.py
+++ b/src/opencode/_service.py
@@ -32,8 +32,8 @@
import httpx
from anyio import sleep as _async_sleep
-from ._constants import DEFAULT_USERNAME
from ._exceptions import OpencodeError
+from .lib._support import DEFAULT_USERNAME
__all__ = [
"BasicAuth",
diff --git a/src/opencode/lib/_ready.py b/src/opencode/lib/_ready.py
index 2035b7c..0ff35bf 100644
--- a/src/opencode/lib/_ready.py
+++ b/src/opencode/lib/_ready.py
@@ -11,7 +11,8 @@
import anyio
-from .._exceptions import APIConnectionError, ServiceUnavailableError
+from .._errors import ServiceUnavailableError
+from .._exceptions import APIConnectionError
if TYPE_CHECKING:
from collections.abc import Iterable
diff --git a/src/opencode/lib/_support.py b/src/opencode/lib/_support.py
new file mode 100644
index 0000000..819e3e5
--- /dev/null
+++ b/src/opencode/lib/_support.py
@@ -0,0 +1,130 @@
+"""Helpers the hand-written modules share, moved here from the SDK's former runtime.
+
+Ferrule's runtime replaced ``_base_client``, ``_exceptions``, ``_qs`` and ``_constants``; what the
+RPC, PTY, server and service modules still need from them lives here, in ``lib/`` where custom
+code is never regenerated.
+"""
+
+from __future__ import annotations
+
+import base64
+import datetime as dt
+import enum
+import functools
+from collections.abc import Mapping, Sequence
+from typing import Any, Union, cast
+from urllib.parse import quote
+
+import httpx
+import pydantic
+
+from .._types import NotGiven, Omit
+
+__all__ = [
+ "DEFAULT_USERNAME",
+ "Timeout",
+ "encode_path",
+ "encode_path_segment",
+ "encode_query",
+ "to_jsonable",
+ "type_adapter",
+ "validation_summary",
+]
+
+DEFAULT_USERNAME = "opencode"
+"""The Basic auth user name the OpenCode server expects."""
+
+Timeout = Union[float, httpx.Timeout, None]
+"""A timeout in seconds, an ``httpx.Timeout``, or ``None`` for none."""
+
+
+@functools.cache
+def _cached_adapter(tp: Any) -> pydantic.TypeAdapter[Any]:
+ return pydantic.TypeAdapter(tp)
+
+
+def type_adapter(tp: Any) -> pydantic.TypeAdapter[Any]:
+ """Returns a cached ``TypeAdapter`` for ``tp``."""
+ try:
+ return _cached_adapter(tp)
+ except TypeError: # unhashable type expression
+ return pydantic.TypeAdapter(tp)
+
+
+def validation_summary(error: pydantic.ValidationError, limit: int = 5) -> str:
+ """Describes a validation failure by location and reason only, never echoing response values."""
+ issues = error.errors(include_input=False, include_url=False)
+ parts = [f"{'.'.join(str(item) for item in issue['loc']) or ''}: {issue['msg']}" for issue in issues[:limit]]
+ more = f" (+{len(issues) - limit} more)" if len(issues) > limit else ""
+ return f"Response did not match the expected schema: {'; '.join(parts)}{more}"
+
+
+def encode_query(params: Mapping[str, object]) -> list[tuple[str, str]]:
+ """Flattens query parameters into ordered ``(key, value)`` pairs."""
+ pairs: list[tuple[str, str]] = []
+ for key, value in params.items():
+ _append(pairs, key, to_jsonable(value))
+ return pairs
+
+
+def _append(pairs: list[tuple[str, str]], key: str, value: object) -> None:
+ if isinstance(value, (NotGiven, Omit)):
+ return
+ if value is None:
+ pairs.append((key, "null"))
+ elif isinstance(value, bool):
+ pairs.append((key, "true" if value else "false"))
+ elif isinstance(value, (list, tuple)):
+ for item in cast("Sequence[object]", value):
+ _append(pairs, key, item)
+ elif isinstance(value, dict):
+ for child, item in cast("dict[str, object]", value).items():
+ _append(pairs, f"{key}[{child}]", item)
+ else:
+ pairs.append((key, str(value)))
+
+
+def encode_path_segment(value: object) -> str:
+ """Encodes one path parameter, escaping ``/`` and every other reserved character.
+
+ ``.`` and ``..`` are percent-encoded too, so URL normalization cannot turn a parameter into a different route.
+ """
+ text = str(value)
+ if text == "":
+ raise ValueError("Path parameters must not be empty")
+ return _encode_part(text)
+
+
+def encode_path(value: str) -> str:
+ """Encodes a trailing wildcard path, keeping ``/`` separators and encoding dot segments."""
+ return "/".join(_encode_part(part) for part in value.split("/"))
+
+
+def _encode_part(part: str) -> str:
+ if part in (".", ".."):
+ return part.replace(".", "%2E")
+ return quote(part, safe="")
+
+
+def to_jsonable(value: Any) -> Any:
+ """Converts models and standard containers to plain JSON values, dropping ``NOT_GIVEN`` entries."""
+ if isinstance(value, pydantic.BaseModel):
+ return value.model_dump(mode="json", by_alias=True, exclude_unset=True)
+ if isinstance(value, (NotGiven, Omit)) or value is None or isinstance(value, (str, int, float, bool)):
+ return value
+ if isinstance(value, Mapping):
+ mapping = cast("Mapping[object, object]", value)
+ return {str(key): to_jsonable(item) for key, item in mapping.items() if not isinstance(item, (NotGiven, Omit))}
+ if isinstance(value, (bytes, bytearray, memoryview)):
+ return base64.b64encode(bytes(cast("bytes", value))).decode("ascii")
+ if isinstance(value, Sequence):
+ return [to_jsonable(item) for item in cast("Sequence[object]", value)]
+ if isinstance(value, (set, frozenset)):
+ return [to_jsonable(item) for item in cast("set[object]", value)]
+ if isinstance(value, enum.Enum):
+ return to_jsonable(value.value)
+ if isinstance(value, (dt.datetime, dt.date)):
+ return value.isoformat()
+ value_type: Any = value.__class__
+ adapter: pydantic.TypeAdapter[Any] = pydantic.TypeAdapter(value_type)
+ return adapter.dump_python(value, mode="json", by_alias=True)
From dc87c4f6d2689a3eedfd5bf22d1b43a3cb58c974 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 16:02:01 +0530
Subject: [PATCH 05/41] Give the config its ten-minute default timeout
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
stainless.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/stainless.yml b/stainless.yml
index 7b56db0..c9d5277 100644
--- a/stainless.yml
+++ b/stainless.yml
@@ -19,6 +19,8 @@ security_schemes:
client_settings:
default_client_name: Opencode
default_env_prefix: OPENCODE
+ # Agent operations run long: ten minutes, as the SDK's own client had.
+ default_timeout: PT10M
opts:
username:
type: string
From 368b72f3963f441bf9f51621d3c9a6de59f644d5 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 16:02:09 +0530
Subject: [PATCH 06/41] chore: regenerate SDK
Ferrule-Generated-From: 49bd8fedd935b934f4c2c9d51a77859611dc5e6e
---
ferrule.lock | 6 +++---
src/opencode/_client.py | 13 +++++++++++--
src/opencode/_constants.py | 4 ++--
3 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/ferrule.lock b/ferrule.lock
index 80edae9..60ce84a 100644
--- a/ferrule.lock
+++ b/ferrule.lock
@@ -1,5 +1,5 @@
{
- "config_sha256": "0365cf56a3497df641b31863dbec8dca95d197446d834125a05757c0f9559b0d",
+ "config_sha256": "a128ae7306666a21300861abeb5eba52316760cfcd8a2ea8118c0e72ad4ad1a2",
"ferrule_version": "0.1.0",
"files": {
".github/workflows/ci.yml": "5ac1be1c103477e7dcdb28d276ff14fb4ebf92650e92d6912e605a64df2dd27b",
@@ -58,9 +58,9 @@
"src/opencode/_agent/SKILL.md": "5f52188520785747cc486ae9f2f094bbe00af72cdc06378c74632439ce3682f2",
"src/opencode/_agent/llms.txt": "c9d8fa2e8580039de36b140ca06f2cb5558ff3d2b42e16effbd3a46ed682eef4",
"src/opencode/_base_client.py": "32888441549574c13267ece8bfbc34394bfb41e6bef39633207fa8381805aae7",
- "src/opencode/_client.py": "2b9828dc575d168465420bb6180ee9fa0c3d68eb2d62c87b3db0abe5d5c0fe22",
+ "src/opencode/_client.py": "d14617ca1aea9d2266756cceba3e5c16c6b9b2b35fcf489a26128549cbec7684",
"src/opencode/_compat.py": "ffd82e41fcd89cbdc336db9d5f95bb4f671d4ac931f3d0790057e13d00f13149",
- "src/opencode/_constants.py": "4b5e0f173c8df7e237d70895ed298894be466b430b1f176f7a022719d5c7eed3",
+ "src/opencode/_constants.py": "16dda1660f42a327c1c1c9da2681dc9d8fcad4bdbbec0930e0a037f20d7b1179",
"src/opencode/_exceptions.py": "2d4b22b9988e3d5cda53564d3be707c509c76385414534b33f638c414a889b7d",
"src/opencode/_files.py": "49562a344d4bfb0626d46722c85f790422460b582c580aaf5ef4b2a2cfbfacda",
"src/opencode/_models.py": "25069c6a3ba112b956602a620daeecfe22b8ac756e6b7e8cf8491a189fcf9ab5",
diff --git a/src/opencode/_client.py b/src/opencode/_client.py
index 852070a..30b3f08 100644
--- a/src/opencode/_client.py
+++ b/src/opencode/_client.py
@@ -3,6 +3,7 @@
from __future__ import annotations
import os
+import base64
from typing import TYPE_CHECKING, Any, Mapping
from typing_extensions import Self, override
@@ -380,7 +381,11 @@ def qs(self) -> Querystring:
@property
@override
def auth_headers(self) -> dict[str, str]:
- return {}
+ if self.username is None or self.password is None:
+ return {}
+ credentials = f"{self.username}:{self.password}".encode("ascii")
+ header = f"Basic {base64.b64encode(credentials).decode('ascii')}"
+ return {"Authorization": header}
@property
@override
@@ -746,7 +751,11 @@ def qs(self) -> Querystring:
@property
@override
def auth_headers(self) -> dict[str, str]:
- return {}
+ if self.username is None or self.password is None:
+ return {}
+ credentials = f"{self.username}:{self.password}".encode("ascii")
+ header = f"Basic {base64.b64encode(credentials).decode('ascii')}"
+ return {"Authorization": header}
@property
@override
diff --git a/src/opencode/_constants.py b/src/opencode/_constants.py
index 6ddf2c7..0d2274d 100644
--- a/src/opencode/_constants.py
+++ b/src/opencode/_constants.py
@@ -5,8 +5,8 @@
RAW_RESPONSE_HEADER = "X-Stainless-Raw-Response"
OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to"
-# default timeout is 1 minute
-DEFAULT_TIMEOUT = httpx.Timeout(timeout=60, connect=5.0)
+# default timeout is 10 minutes
+DEFAULT_TIMEOUT = httpx.Timeout(timeout=600, connect=5.0)
DEFAULT_MAX_RETRIES = 2
DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20)
From be0720b0b769683c5794f2f673fadc267d9e8631 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 16:26:40 +0530
Subject: [PATCH 07/41] Unwrap the envelopes the former generator unwrapped
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
openapi.json | 108 ++++++++++++++++++++++++-------------
stainless.yml | 144 +++++++++++++++++++++++++++++++++++++-------------
2 files changed, 180 insertions(+), 72 deletions(-)
diff --git a/openapi.json b/openapi.json
index 8ebc70c..3b78c9a 100644
--- a/openapi.json
+++ b/openapi.json
@@ -624,7 +624,8 @@
},
"x-opencode-client-path": [
"list"
- ]
+ ],
+ "x-opencode-unwrap-data": true
},
"post": {
"operationId": "credential.create",
@@ -717,7 +718,8 @@
},
"x-opencode-client-path": [
"create"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/credential/{credentialID}": {
@@ -1326,7 +1328,8 @@
},
"x-opencode-client-path": [
"text"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/experimental/integration/wellknown": {
@@ -2153,7 +2156,8 @@
"x-opencode-client-path": [
"persistentPty",
"get"
- ]
+ ],
+ "x-opencode-unwrap-data": true
},
"put": {
"operationId": "server.experimental.persistentPty.update",
@@ -2270,7 +2274,8 @@
"x-opencode-client-path": [
"persistentPty",
"update"
- ]
+ ],
+ "x-opencode-unwrap-data": true
},
"delete": {
"operationId": "server.experimental.persistentPty.remove",
@@ -2443,7 +2448,8 @@
"x-opencode-client-path": [
"persistentPty",
"connectToken"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/experimental/persistent-pty/{ptyID}/snapshot": {
@@ -2526,7 +2532,8 @@
"x-opencode-client-path": [
"persistentPty",
"snapshot"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/experimental/session/import": {
@@ -2632,7 +2639,8 @@
},
"x-opencode-client-path": [
"import"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/experimental/session/stats": {
@@ -2792,7 +2800,8 @@
},
"x-opencode-client-path": [
"stats"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/experimental/session/{sessionID}/export": {
@@ -2891,7 +2900,8 @@
},
"x-opencode-client-path": [
"export"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/experimental/session/{sessionID}/instructions/entries": {
@@ -2977,7 +2987,8 @@
"instructions",
"entry",
"list"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/experimental/session/{sessionID}/instructions/entries/{key}": {
@@ -3418,7 +3429,8 @@
"x-opencode-client-path": [
"persistentPty",
"list"
- ]
+ ],
+ "x-opencode-unwrap-data": true
},
"post": {
"operationId": "server.experimental.persistentPty.create",
@@ -3545,7 +3557,8 @@
"x-opencode-client-path": [
"persistentPty",
"create"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/experimental/session/{sessionID}/terminal/read": {
@@ -3642,7 +3655,8 @@
"x-opencode-client-path": [
"persistentPty",
"read"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/experimental/session/{sessionID}/wait": {
@@ -3814,7 +3828,8 @@
},
"x-opencode-client-path": [
"session"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/form": {
@@ -6493,7 +6508,8 @@
"x-opencode-client-path": [
"saved",
"list"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/permission/saved/{id}": {
@@ -6617,7 +6633,8 @@
"x-opencode-client-path": [
"saved",
"update"
- ]
+ ],
+ "x-opencode-unwrap-data": true
},
"delete": {
"operationId": "permission.saved.remove",
@@ -8746,7 +8763,8 @@
},
"x-opencode-client-path": [
"create"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/session/active": {
@@ -8806,7 +8824,8 @@
},
"x-opencode-client-path": [
"active"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/session/{sessionID}": {
@@ -8880,7 +8899,8 @@
},
"x-opencode-client-path": [
"get"
- ]
+ ],
+ "x-opencode-unwrap-data": true
},
"delete": {
"operationId": "session.remove",
@@ -9451,7 +9471,8 @@
},
"x-opencode-client-path": [
"compact"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/session/{sessionID}/context": {
@@ -9538,7 +9559,8 @@
},
"x-opencode-client-path": [
"context"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/session/{sessionID}/diff": {
@@ -9684,7 +9706,8 @@
},
"x-opencode-client-path": [
"diff"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/session/{sessionID}/environment": {
@@ -9869,7 +9892,8 @@
},
"x-opencode-client-path": [
"fork"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/session/{sessionID}/form": {
@@ -9954,7 +9978,8 @@
"x-opencode-client-path": [
"form",
"list"
- ]
+ ],
+ "x-opencode-unwrap-data": true
},
"post": {
"operationId": "session.form.create",
@@ -10079,7 +10104,8 @@
"x-opencode-client-path": [
"form",
"create"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/session/{sessionID}/form/{formID}": {
@@ -10172,7 +10198,8 @@
"x-opencode-client-path": [
"form",
"get"
- ]
+ ],
+ "x-opencode-unwrap-data": true
},
"delete": {
"operationId": "session.form.cancel",
@@ -10484,7 +10511,8 @@
},
"x-opencode-client-path": [
"generate"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/session/{sessionID}/inbox": {
@@ -10562,7 +10590,8 @@
"x-opencode-client-path": [
"inbox",
"list"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/session/{sessionID}/inbox/{inboxID}": {
@@ -11064,7 +11093,8 @@
"x-opencode-client-path": [
"message",
"get"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/session/{sessionID}/model": {
@@ -11384,7 +11414,8 @@
},
"x-opencode-client-path": [
"create"
- ]
+ ],
+ "x-opencode-unwrap-data": true
},
"get": {
"operationId": "session.permission.list",
@@ -11466,7 +11497,8 @@
},
"x-opencode-client-path": [
"list"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/session/{sessionID}/permission/{requestID}": {
@@ -11558,7 +11590,8 @@
},
"x-opencode-client-path": [
"get"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/session/{sessionID}/permission/{requestID}/reply": {
@@ -11822,7 +11855,8 @@
},
"x-opencode-client-path": [
"prompt"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/session/{sessionID}/revert": {
@@ -12115,7 +12149,8 @@
"x-opencode-client-path": [
"revert",
"stage"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/session/{sessionID}/shell": {
@@ -12360,7 +12395,8 @@
},
"x-opencode-client-path": [
"synthetic"
- ]
+ ],
+ "x-opencode-unwrap-data": true
}
},
"/api/session/{sessionID}/view": {
diff --git a/stainless.yml b/stainless.yml
index c9d5277..df40228 100644
--- a/stainless.yml
+++ b/stainless.yml
@@ -554,8 +554,12 @@ resources:
update: patch /api/experimental/config
credential:
methods:
- list: get /api/credential
- create: post /api/credential
+ list:
+ endpoint: get /api/credential
+ unwrap_response: data
+ create:
+ endpoint: post /api/credential
+ unwrap_response: data
update: patch /api/credential/{credentialID}
activate: post /api/credential/{credentialID}/activate
remove: delete /api/credential/{credentialID}
@@ -577,16 +581,30 @@ resources:
subresources:
persistent_pty:
methods:
- read: get /api/experimental/session/{sessionID}/terminal/read
- list: get /api/experimental/session/{sessionID}/terminal
- create: post /api/experimental/session/{sessionID}/terminal
+ read:
+ endpoint: get /api/experimental/session/{sessionID}/terminal/read
+ unwrap_response: data
+ list:
+ endpoint: get /api/experimental/session/{sessionID}/terminal
+ unwrap_response: data
+ create:
+ endpoint: post /api/experimental/session/{sessionID}/terminal
+ unwrap_response: data
shutdown: post /api/experimental/persistent-pty/shutdown
handoff: post /api/experimental/persistent-pty/handoff
- get: get /api/experimental/persistent-pty/{ptyID}
- update: put /api/experimental/persistent-pty/{ptyID}
- snapshot: get /api/experimental/persistent-pty/{ptyID}/snapshot
+ get:
+ endpoint: get /api/experimental/persistent-pty/{ptyID}
+ unwrap_response: data
+ update:
+ endpoint: put /api/experimental/persistent-pty/{ptyID}
+ unwrap_response: data
+ snapshot:
+ endpoint: get /api/experimental/persistent-pty/{ptyID}/snapshot
+ unwrap_response: data
remove: delete /api/experimental/persistent-pty/{ptyID}
- connect_token: post /api/experimental/persistent-pty/{ptyID}/connect-token
+ connect_token:
+ endpoint: post /api/experimental/persistent-pty/{ptyID}/connect-token
+ unwrap_response: data
file:
methods:
read: get /api/fs/read/{path}
@@ -598,7 +616,9 @@ resources:
list: get /api/form
generate:
methods:
- text: post /api/experimental/generate
+ text:
+ endpoint: post /api/experimental/generate
+ unwrap_response: data
integration:
methods:
list: get /api/integration
@@ -651,9 +671,15 @@ resources:
default: get /api/model/default
permission:
methods:
- create: post /api/session/{sessionID}/permission
- list: get /api/session/{sessionID}/permission
- get: get /api/session/{sessionID}/permission/{requestID}
+ create:
+ endpoint: post /api/session/{sessionID}/permission
+ unwrap_response: data
+ list:
+ endpoint: get /api/session/{sessionID}/permission
+ unwrap_response: data
+ get:
+ endpoint: get /api/session/{sessionID}/permission/{requestID}
+ unwrap_response: data
reply: post /api/session/{sessionID}/permission/{requestID}/reply
subresources:
request_:
@@ -661,8 +687,12 @@ resources:
list: get /api/permission/request
saved:
methods:
- list: get /api/permission/saved
- update: patch /api/permission/saved/{id}
+ list:
+ endpoint: get /api/permission/saved
+ unwrap_response: data
+ update:
+ endpoint: patch /api/permission/saved/{id}
+ unwrap_response: data
remove: delete /api/permission/saved/{id}
plugin:
methods:
@@ -702,28 +732,54 @@ resources:
session:
methods:
list: get /api/session
- stats: get /api/experimental/session/stats
- create: post /api/session
- import_: post /api/experimental/session/import
- export: get /api/experimental/session/{sessionID}/export
- active: get /api/session/active
- get: get /api/session/{sessionID}
+ stats:
+ endpoint: get /api/experimental/session/stats
+ unwrap_response: data
+ create:
+ endpoint: post /api/session
+ unwrap_response: data
+ import_:
+ endpoint: post /api/experimental/session/import
+ unwrap_response: data
+ export:
+ endpoint: get /api/experimental/session/{sessionID}/export
+ unwrap_response: data
+ active:
+ endpoint: get /api/session/active
+ unwrap_response: data
+ get:
+ endpoint: get /api/session/{sessionID}
+ unwrap_response: data
remove: delete /api/session/{sessionID}
- fork: post /api/session/{sessionID}/fork
+ fork:
+ endpoint: post /api/session/{sessionID}/fork
+ unwrap_response: data
switch_agent: post /api/session/{sessionID}/agent
switch_model: post /api/session/{sessionID}/model
update: patch /api/session/{sessionID}
move: post /api/session/{sessionID}/move
- prompt: post /api/session/{sessionID}/prompt
+ prompt:
+ endpoint: post /api/session/{sessionID}/prompt
+ unwrap_response: data
command: post /api/session/{sessionID}/command
skill: post /api/experimental/session/{sessionID}/skill
- synthetic: post /api/session/{sessionID}/synthetic
+ synthetic:
+ endpoint: post /api/session/{sessionID}/synthetic
+ unwrap_response: data
shell: post /api/session/{sessionID}/shell
- compact: post /api/session/{sessionID}/compact
+ compact:
+ endpoint: post /api/session/{sessionID}/compact
+ unwrap_response: data
wait: post /api/experimental/session/{sessionID}/wait
- context: get /api/session/{sessionID}/context
- diff: get /api/session/{sessionID}/diff
- generate: post /api/session/{sessionID}/generate
+ context:
+ endpoint: get /api/session/{sessionID}/context
+ unwrap_response: data
+ diff:
+ endpoint: get /api/session/{sessionID}/diff
+ unwrap_response: data
+ generate:
+ endpoint: post /api/session/{sessionID}/generate
+ unwrap_response: data
log: get /api/experimental/session/{sessionID}/log
interrupt: post /api/session/{sessionID}/interrupt
background: post /api/session/{sessionID}/background
@@ -732,29 +788,43 @@ resources:
subresources:
revert:
methods:
- stage: post /api/session/{sessionID}/revert/stage
+ stage:
+ endpoint: post /api/session/{sessionID}/revert/stage
+ unwrap_response: data
clear: delete /api/session/{sessionID}/revert
commit: post /api/session/{sessionID}/revert/commit
inbox:
methods:
- list: get /api/session/{sessionID}/inbox
+ list:
+ endpoint: get /api/session/{sessionID}/inbox
+ unwrap_response: data
cancel: delete /api/session/{sessionID}/inbox/{inboxID}
update: patch /api/session/{sessionID}/inbox/{inboxID}
instructions:
subresources:
entry:
methods:
- list: get /api/experimental/session/{sessionID}/instructions/entries
+ list:
+ endpoint: get /api/experimental/session/{sessionID}/instructions/entries
+ unwrap_response: data
put: put /api/experimental/session/{sessionID}/instructions/entries/{key}
remove: delete /api/experimental/session/{sessionID}/instructions/entries/{key}
message:
methods:
- get: get /api/session/{sessionID}/message/{messageID}
+ get:
+ endpoint: get /api/session/{sessionID}/message/{messageID}
+ unwrap_response: data
form:
methods:
- list: get /api/session/{sessionID}/form
- create: post /api/session/{sessionID}/form
- get: get /api/session/{sessionID}/form/{formID}
+ list:
+ endpoint: get /api/session/{sessionID}/form
+ unwrap_response: data
+ create:
+ endpoint: post /api/session/{sessionID}/form
+ unwrap_response: data
+ get:
+ endpoint: get /api/session/{sessionID}/form/{formID}
+ unwrap_response: data
reply: post /api/session/{sessionID}/form/{formID}/reply
cancel: delete /api/session/{sessionID}/form/{formID}
shell:
@@ -780,7 +850,9 @@ resources:
list: get /api/vcs/branch
voice:
methods:
- session: post /api/experimental/voice/session
+ session:
+ endpoint: post /api/experimental/voice/session
+ unwrap_response: data
websearch:
methods:
providers: get /api/websearch/provider
From 16dcce04d5a54bdc073b6db6531870a9b9b6fec7 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 16:26:55 +0530
Subject: [PATCH 08/41] chore: regenerate SDK
Ferrule-Generated-From: 382aeccd7b8215215a5cd013e8c2888acbd29dc7
---
api.md | 100 +++-----
ferrule.lock | 107 ++++----
src/opencode/_wrappers.py | 17 ++
src/opencode/resources/credential.py | 41 ++-
.../resources/experimental/persistent_pty.py | 133 ++++++----
src/opencode/resources/generate.py | 21 +-
.../resources/permission/permission.py | 57 +++--
src/opencode/resources/permission/saved.py | 31 ++-
src/opencode/resources/session/form.py | 63 +++--
src/opencode/resources/session/inbox.py | 19 +-
.../resources/session/instructions/entry.py | 19 +-
src/opencode/resources/session/message.py | 25 +-
src/opencode/resources/session/revert.py | 25 +-
src/opencode/resources/session/session.py | 233 ++++++++++++------
src/opencode/resources/voice.py | 21 +-
src/opencode/types/__init__.py | 14 +-
.../types/credential_create_response.py | 26 --
.../types/credential_list_response.py | 22 +-
src/opencode/types/experimental/__init__.py | 7 -
.../persistent_pty_connect_token_response.py | 26 --
.../persistent_pty_create_response.py | 26 --
.../persistent_pty_get_response.py | 26 --
.../persistent_pty_list_response.py | 22 +-
.../persistent_pty_read_response.py | 22 +-
.../persistent_pty_snapshot_response.py | 26 --
.../persistent_pty_update_response.py | 26 --
..._response.py => generate_text_response.py} | 7 +-
src/opencode/types/permission/__init__.py | 1 -
.../types/permission/saved_list_response.py | 22 +-
.../types/permission/saved_update_response.py | 26 --
.../types/permission_create_response.py | 20 +-
src/opencode/types/permission_get_response.py | 26 --
.../types/permission_list_response.py | 22 +-
src/opencode/types/session/__init__.py | 4 -
.../types/session/form_create_response.py | 26 --
.../types/session/form_get_response.py | 26 --
.../types/session/form_list_response.py | 22 +-
.../types/session/inbox_list_response.py | 22 +-
.../instructions/entry_list_response.py | 22 +-
.../types/session/message_get_response.py | 26 --
.../types/session/revert_stage_response.py | 26 --
src/opencode/types/session_active_response.py | 23 +-
.../types/session_compact_response.py | 26 --
.../types/session_context_response.py | 22 +-
src/opencode/types/session_create_response.py | 26 --
src/opencode/types/session_diff_response.py | 22 +-
src/opencode/types/session_export_response.py | 26 --
...sponse.py => session_generate_response.py} | 7 +-
src/opencode/types/session_prompt_response.py | 26 --
src/opencode/types/session_stats_response.py | 26 --
.../types/session_synthetic_response.py | 26 --
..._response.py => voice_session_response.py} | 8 +-
.../experimental/test_persistent_pty.py | 82 +++---
tests/api_resources/permission/test_saved.py | 19 +-
tests/api_resources/session/test_form.py | 31 +--
tests/api_resources/session/test_message.py | 14 +-
tests/api_resources/session/test_revert.py | 18 +-
tests/api_resources/test_credential.py | 19 +-
tests/api_resources/test_generate.py | 2 +-
tests/api_resources/test_permission.py | 19 +-
tests/api_resources/test_session.py | 156 ++++++------
tests/api_resources/test_voice.py | 2 +-
62 files changed, 810 insertions(+), 1243 deletions(-)
create mode 100644 src/opencode/_wrappers.py
delete mode 100644 src/opencode/types/credential_create_response.py
delete mode 100644 src/opencode/types/experimental/persistent_pty_connect_token_response.py
delete mode 100644 src/opencode/types/experimental/persistent_pty_create_response.py
delete mode 100644 src/opencode/types/experimental/persistent_pty_get_response.py
delete mode 100644 src/opencode/types/experimental/persistent_pty_snapshot_response.py
delete mode 100644 src/opencode/types/experimental/persistent_pty_update_response.py
rename src/opencode/types/{session_get_response.py => generate_text_response.py} (85%)
delete mode 100644 src/opencode/types/permission/saved_update_response.py
delete mode 100644 src/opencode/types/permission_get_response.py
delete mode 100644 src/opencode/types/session/form_create_response.py
delete mode 100644 src/opencode/types/session/form_get_response.py
delete mode 100644 src/opencode/types/session/message_get_response.py
delete mode 100644 src/opencode/types/session/revert_stage_response.py
delete mode 100644 src/opencode/types/session_compact_response.py
delete mode 100644 src/opencode/types/session_create_response.py
delete mode 100644 src/opencode/types/session_export_response.py
rename src/opencode/types/{session_fork_response.py => session_generate_response.py} (85%)
delete mode 100644 src/opencode/types/session_prompt_response.py
delete mode 100644 src/opencode/types/session_stats_response.py
delete mode 100644 src/opencode/types/session_synthetic_response.py
rename src/opencode/types/{session_import__response.py => voice_session_response.py} (84%)
diff --git a/api.md b/api.md
index cbec1c8..071295d 100644
--- a/api.md
+++ b/api.md
@@ -409,12 +409,12 @@ Methods:
Types:
```python
-from opencode.types import CredentialCreateResponse, CredentialListResponse
+from opencode.types import CredentialListResponse
```
Methods:
-- client.credential.create(\*\*params) -> CredentialCreateResponse
+- client.credential.create(\*\*params) -> CredentialEntry
- client.credential.update(credential_id, \*\*params) -> None
- client.credential.list() -> CredentialListResponse
- client.credential.activate(credential_id) -> None
@@ -449,29 +449,24 @@ Types:
```python
from opencode.types.experimental import (
- PersistentPtyCreateResponse,
- PersistentPtyUpdateResponse,
PersistentPtyListResponse,
- PersistentPtyConnectTokenResponse,
- PersistentPtyGetResponse,
PersistentPtyHandoffResponse,
PersistentPtyReadResponse,
- PersistentPtySnapshotResponse,
)
```
Methods:
-- client.experimental.persistent_pty.create(session_id, \*\*params) -> PersistentPtyCreateResponse
-- client.experimental.persistent_pty.update(pty_id, \*\*params) -> PersistentPtyUpdateResponse
+- client.experimental.persistent_pty.create(session_id, \*\*params) -> PersistentPtyInfo
+- client.experimental.persistent_pty.update(pty_id, \*\*params) -> PersistentPtyInfo
- client.experimental.persistent_pty.list(session_id) -> PersistentPtyListResponse
-- client.experimental.persistent_pty.connect_token(pty_id) -> PersistentPtyConnectTokenResponse
-- client.experimental.persistent_pty.get(pty_id) -> PersistentPtyGetResponse
+- client.experimental.persistent_pty.connect_token(pty_id) -> PtyTicketConnectToken
+- client.experimental.persistent_pty.get(pty_id) -> PersistentPtyInfo
- client.experimental.persistent_pty.handoff() -> PersistentPtyHandoffResponse
- client.experimental.persistent_pty.read(session_id, \*\*params) -> PersistentPtyReadResponse
- client.experimental.persistent_pty.remove(pty_id) -> None
- client.experimental.persistent_pty.shutdown() -> None
-- client.experimental.persistent_pty.snapshot(pty_id) -> PersistentPtySnapshotResponse
+- client.experimental.persistent_pty.snapshot(pty_id) -> PersistentPtySnapshot
# File
@@ -502,9 +497,15 @@ Methods:
# Generate
+Types:
+
+```python
+from opencode.types import GenerateTextResponse
+```
+
Methods:
-- client.generate.text(\*\*params) -> GenerateTextResponse
+- client.generate.text(\*\*params) -> GenerateTextResponse
# Integration
@@ -634,14 +635,14 @@ Methods:
Types:
```python
-from opencode.types import PermissionCreateResponse, PermissionListResponse, PermissionGetResponse
+from opencode.types import PermissionCreateResponse, PermissionListResponse
```
Methods:
- client.permission.create(session_id, \*\*params) -> PermissionCreateResponse
- client.permission.list(session_id) -> PermissionListResponse
-- client.permission.get(request_id, \*, session_id) -> PermissionGetResponse
+- client.permission.get(request_id, \*, session_id) -> PermissionRequest
- client.permission.reply(request_id, \*, session_id, \*\*params) -> None
## Request
@@ -661,12 +662,12 @@ Methods:
Types:
```python
-from opencode.types.permission import SavedUpdateResponse, SavedListResponse
+from opencode.types.permission import SavedListResponse
```
Methods:
-- client.permission.saved.update(id, \*\*params) -> SavedUpdateResponse
+- client.permission.saved.update(id, \*\*params) -> PermissionSavedInfo
- client.permission.saved.list(\*\*params) -> SavedListResponse
- client.permission.saved.remove(id) -> None
@@ -769,66 +770,47 @@ Methods:
Types:
```python
-from opencode.types import (
- SessionCreateResponse,
- SessionActiveResponse,
- SessionCompactResponse,
- SessionContextResponse,
- SessionDiffResponse,
- SessionExportResponse,
- SessionForkResponse,
- SessionGetResponse,
- SessionImportResponse,
- SessionPromptResponse,
- SessionStatsResponse,
- SessionSyntheticResponse,
-)
+from opencode.types import SessionActiveResponse, SessionContextResponse, SessionDiffResponse, SessionGenerateResponse
```
Methods:
-- client.session.create(\*\*params) -> SessionCreateResponse
+- client.session.create(\*\*params) -> SessionInfo
- client.session.update(session_id, \*\*params) -> None
- client.session.list(\*\*params) -> SessionInfo
- client.session.active() -> SessionActiveResponse
- client.session.background(session_id) -> None
- client.session.command(session_id, \*\*params) -> None
-- client.session.compact(session_id, \*\*params) -> SessionCompactResponse
+- client.session.compact(session_id, \*\*params) -> SessionInboxCompaction
- client.session.context(session_id) -> SessionContextResponse
- client.session.diff(session_id, \*\*params) -> SessionDiffResponse
- client.session.environment(session_id, \*\*params) -> None
-- client.session.export(session_id, \*\*params) -> SessionExportResponse
-- client.session.fork(session_id, \*\*params) -> SessionForkResponse
-- client.session.generate(session_id, \*\*params) -> SessionGenerateResponse
-- client.session.get(session_id) -> SessionGetResponse
-- client.session.import_(\*\*params) -> SessionImportResponse
+- client.session.export(session_id, \*\*params) -> SessionTransferData
+- client.session.fork(session_id, \*\*params) -> SessionInfo
+- client.session.generate(session_id, \*\*params) -> SessionGenerateResponse
+- client.session.get(session_id) -> SessionInfo
+- client.session.import_(\*\*params) -> SessionInfo
- client.session.interrupt(session_id, \*\*params) -> SessionInterruptResponse
- client.session.log(session_id, \*\*params) -> None
- client.session.move(session_id, \*\*params) -> None
-- client.session.prompt(session_id, \*\*params) -> SessionPromptResponse
+- client.session.prompt(session_id, \*\*params) -> SessionInboxUser
- client.session.remove(session_id) -> None
- client.session.shell(session_id, \*\*params) -> None
- client.session.skill(session_id, \*\*params) -> None
-- client.session.stats(\*\*params) -> SessionStatsResponse
+- client.session.stats(\*\*params) -> SessionStatsInfo
- client.session.switch_agent(session_id, \*\*params) -> None
- client.session.switch_model(session_id, \*\*params) -> None
-- client.session.synthetic(session_id, \*\*params) -> SessionSyntheticResponse
+- client.session.synthetic(session_id, \*\*params) -> SessionInboxSynthetic
- client.session.view(session_id, \*\*params) -> None
- client.session.wait(session_id) -> None
## Revert
-Types:
-
-```python
-from opencode.types.session import RevertStageResponse
-```
-
Methods:
- client.session.revert.clear(session_id) -> None
- client.session.revert.commit(session_id) -> None
-- client.session.revert.stage(session_id, \*\*params) -> RevertStageResponse
+- client.session.revert.stage(session_id, \*\*params) -> SessionRevert
## Inbox
@@ -862,30 +844,24 @@ Methods:
## Message
-Types:
-
-```python
-from opencode.types.session import MessageGetResponse
-```
-
Methods:
-- client.session.message.get(message_id, \*, session_id) -> MessageGetResponse
+- client.session.message.get(message_id, \*, session_id) -> SessionMessageInfo
## Form
Types:
```python
-from opencode.types.session import FormCreateResponse, FormListResponse, FormGetResponse
+from opencode.types.session import FormListResponse
```
Methods:
-- client.session.form.create(session_id, \*\*params) -> FormCreateResponse
+- client.session.form.create(session_id, \*\*params) -> FormInfo
- client.session.form.list(session_id) -> FormListResponse
- client.session.form.cancel(form_id, \*, session_id, \*\*params) -> None
-- client.session.form.get(form_id, \*, session_id) -> FormGetResponse
+- client.session.form.get(form_id, \*, session_id) -> FormDetail
- client.session.form.reply(form_id, \*, session_id, \*\*params) -> None
# Shell
@@ -946,9 +922,15 @@ Methods:
# Voice
+Types:
+
+```python
+from opencode.types import VoiceSessionResponse
+```
+
Methods:
-- client.voice.session(\*\*params) -> VoiceSessionResponse
+- client.voice.session(\*\*params) -> VoiceSessionResponse
# Websearch
diff --git a/ferrule.lock b/ferrule.lock
index 60ce84a..53d112a 100644
--- a/ferrule.lock
+++ b/ferrule.lock
@@ -1,12 +1,12 @@
{
- "config_sha256": "a128ae7306666a21300861abeb5eba52316760cfcd8a2ea8118c0e72ad4ad1a2",
+ "config_sha256": "4e800af8877cf4c5cb8c662b42cadbf61a9a696ff26f88c525a499ac3547344f",
"ferrule_version": "0.1.0",
"files": {
".github/workflows/ci.yml": "5ac1be1c103477e7dcdb28d276ff14fb4ebf92650e92d6912e605a64df2dd27b",
".github/workflows/publish-pypi.yml": "9d8ec64e69efcd63590be59b6aaa38669da06b8d1e473872fa82e6b572ea1626",
".github/workflows/release-doctor.yml": "f1dcc10ac7b6f434cf8a6bcdfc3d069c6ff00f65f09e1274986816f38f05993d",
"AGENTS.md": "7f135189e8ab203aea9abecf018f905e395767cd38c24c2d22c3c1230c04c5e5",
- "api.md": "e6f37671004a04c7440240bf3dd6fce0620f6d10a32cfc84f54cb1ccdfe160b0",
+ "api.md": "ebffd584aab5b25583dafdf80606b3283d711c49c844666df908d7e19d62f4fa",
"examples/agent/agent_list.py": "8f50db0f50024f2b1b315f8f6c08f825445172fbef62b6350751498a2e1db9a9",
"examples/agent/command_list.py": "ebfe36957eed36b76d44ac4a867c847ad1f145b0a2f6959c4157f28617b15f7d",
"examples/agent/config_get.py": "f7765272fe31984e76ecc92db552ec7c6cf1e7d046c11d2a09f4a85af1f25a89",
@@ -83,23 +83,24 @@
"src/opencode/_utils/_transform.py": "3630b39a77ea62bb008a450742283a37d41fb9355b2a2a6e3f7babf6670d17e1",
"src/opencode/_utils/_typing.py": "37fe4f3ee14db1aca06ed03f9e965df7c4953752d042f15328be9b9163c16465",
"src/opencode/_utils/_utils.py": "da73cecc3acf7b61800e92c244ce1b3504bc32eecb8c48821bf2c9d8000bea38",
+ "src/opencode/_wrappers.py": "c653648cf562d75c1d342ab4d41cd6c23fc4d22f025a7bf1cbdccb7cfaff9025",
"src/opencode/pagination.py": "938fe8d88193c475cf9b7934b2e50de690f2060754e4458dfdd6657875483339",
"src/opencode/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"src/opencode/resources/__init__.py": "d726664313307373400dc208debb419b7c59b91f759629a8a0b880bf4d0a110c",
"src/opencode/resources/agent.py": "ce008b84dae0db6c167c846b0a9bc2010f27fa2fbc072444e0210c77594d3600",
"src/opencode/resources/command.py": "71817de475ecff45179c8be3440307ddb9e76196f4b200057d0ccea1fbed5505",
"src/opencode/resources/config.py": "df61a96eca71052b06735ccd78a9d9c30bdf9663a25fd42b6e3cd21637177390",
- "src/opencode/resources/credential.py": "a25612e84b8732fb208adf3b1f8831255ec6eb791321b67a2e86107f226a5a60",
+ "src/opencode/resources/credential.py": "302d2270736c1835f2a2ffe926cbc990271b54387135a223e1bb0521d8ed7baf",
"src/opencode/resources/debug/__init__.py": "4b259cbe61b2fece4ebf71e4287a286f8440020daf88219543a9c7dd3038ec82",
"src/opencode/resources/debug/debug.py": "3257e35af342049cbb2377d97a78136bb35793eb89b1cacfed4e019bf024c2bd",
"src/opencode/resources/debug/location.py": "314e488618b6b3aa5d8528edad62c5bc98a1b085c3fe94a8077847ce4e8a25b1",
"src/opencode/resources/event.py": "1726d900bda94f6a9dda4929787e96972b8d5e11492b9294c5be25e2c11af854",
"src/opencode/resources/experimental/__init__.py": "28feac464d3d9902214d3960dede255c9564614645757eae8faa7df956ab4877",
"src/opencode/resources/experimental/experimental.py": "30e7249ccce9f09e96fe456a89e6f8e603769bb7438f195a984c3824e20960c5",
- "src/opencode/resources/experimental/persistent_pty.py": "0b38d2845d715266b51e9ce1051fbb271a724fdbb229c38295ef5ef1688a291e",
+ "src/opencode/resources/experimental/persistent_pty.py": "f3ac11fe8ab6c560393e7928714c2103219ccab33038f36e4043069d37ebf4ac",
"src/opencode/resources/file.py": "d33d2de768514bec732c430628ba04715be7917ee417abe8da38fd7e22f239af",
"src/opencode/resources/form.py": "f10e411104e93a9ac2c57fd9e3b8c93610a8ccd873e98e5c87345f48c0534664",
- "src/opencode/resources/generate.py": "d835b2e9cd29cd06402bb493245e161649f01151ae0075d66335feae3d68bb64",
+ "src/opencode/resources/generate.py": "294617a0360500b3d1b27ab7f64c0864c8d0d6f3ba75f161650c68f54381639c",
"src/opencode/resources/integration/__init__.py": "0f3f50a21c60d150dfee890354599cec64af5220c7916a2000dd2daab0bfcd6d",
"src/opencode/resources/integration/command.py": "0509872d6e3026362446d4bd49512c4419e94bdf99f724d65368a678a278f5c7",
"src/opencode/resources/integration/connect.py": "634655e1240ee0c520289a470b62ba78908543c984af32cdd004ec5adff16887",
@@ -116,9 +117,9 @@
"src/opencode/resources/migration/v1.py": "054977ec18a7974e22f870f203652286406e3af93e4131dcd5b04ce8af764026",
"src/opencode/resources/model.py": "2ed756685b0a5967075474e428f82cdea42bb37067e8eb0985ad3afe970be705",
"src/opencode/resources/permission/__init__.py": "fb877b53463f24574016eb09ec406c43bcce5207e0ceb25c2ee6be02f755486b",
- "src/opencode/resources/permission/permission.py": "9f8fd1266acd4291df37c462dfb722f170d32cd8696d7a1e53b5eec7af540180",
+ "src/opencode/resources/permission/permission.py": "3cd39a05103aba8db3d8a373a1ca182053a11dea335e2d2ef90c26bd91b58cf9",
"src/opencode/resources/permission/request.py": "ddf2ebb3ef0eb186da16e91e65a4f7bdd43058f6595b780755d6e08b41e73f39",
- "src/opencode/resources/permission/saved.py": "15bdd9803cf848b6c006a6513bcfadd5dc8c33245a243d7a835212d145609b54",
+ "src/opencode/resources/permission/saved.py": "5a432d57deea13a08dd589a10fb4c1d51e78d08fba30e2cf3453bc014dcddced",
"src/opencode/resources/plugin.py": "adddbafb0b0aabc81959183ced02aa4e7975a99a1c3f46e9b9c687044086710c",
"src/opencode/resources/project.py": "c95e35f9109077334c69c810fb163209802680ab3c50aa700f530f2a7161f38b",
"src/opencode/resources/provider.py": "070779334044ad73e7292e2d12186e0af5ffc827283312c8a6f1d8e52997e5a3",
@@ -129,23 +130,23 @@
"src/opencode/resources/rpc.py": "f9dd500e3379befee9978c09bcd6dbad8059189f4f06682094c54dc46b57f886",
"src/opencode/resources/server.py": "cf422b830f84978a4bb7626c8ec130bfb65e8c49670de4c5f52a17547ddb306f",
"src/opencode/resources/session/__init__.py": "acc72ffc26560ac86e9c50583c07264fb140d4168afb9b535368eda32d2283ec",
- "src/opencode/resources/session/form.py": "7255ffa6b2a2176ef93640035398b72b3e888ce2a17b9c845d994fd7d5ad6c9f",
- "src/opencode/resources/session/inbox.py": "d6a4a27359b6f8edab6c45cf3989a4bea579c473007820074a9cc3f1c4a80585",
+ "src/opencode/resources/session/form.py": "3afc878622a14e493e0b57bd4365d2ab670f1fa7c6d46db65a0c490396ec0720",
+ "src/opencode/resources/session/inbox.py": "203bfbbe962e5219f5cf77e2a0aea78f876ca2ab370a9a78f27b955bfdc624e6",
"src/opencode/resources/session/instructions/__init__.py": "0f5a6aead7a12d2a4f73046d763f3e65444201724cc069e6793e97a22e34c22d",
- "src/opencode/resources/session/instructions/entry.py": "e5cc3064bd11fb2c2319a9a0abcc17b64b4128377b5dba70ae882367fad612c6",
+ "src/opencode/resources/session/instructions/entry.py": "bdd09fbe179f1d6ffe95d847d831f50232106bb9e358eb36efa842ab29d09cc0",
"src/opencode/resources/session/instructions/instructions.py": "af2fca01a9c6ee019b23871a86c48aeb65a8ab10d8fa50ecaa87247fef4918bc",
- "src/opencode/resources/session/message.py": "2b137d3057fc3fa823eef96741bbee821df4d301608f6bdb6594e01a0df36a44",
- "src/opencode/resources/session/revert.py": "102115b4c796bcdd27f24606c942eba225b7692e91b41f60dd4895d8baca646c",
- "src/opencode/resources/session/session.py": "8d80e75b248870d2e8793e6b8c54505488b17cf2a2d175970ba6287cf6f26976",
+ "src/opencode/resources/session/message.py": "a61c77f68f3bbd1a866150f76ffb473f77c1f6a1a3ff8ea3f19c091967951069",
+ "src/opencode/resources/session/revert.py": "41b29e4f16f446e5dcb72cc3856fe1c4933bb9ca6fb64648715d7cce6f6ffb90",
+ "src/opencode/resources/session/session.py": "71b7af4a763d850b13ad02134515049e05454c3f0e38fbe6fefd8628d2f5be9e",
"src/opencode/resources/shell.py": "cdfaa416d7a57625c1ceeef94498ef74034a9e83b09283e78429ac5e5d07ab10",
"src/opencode/resources/skill.py": "9bd25b6a394f8b741d7b3d22bfdbdd8222e72569c3ae66be2f15aa03efdd7af2",
"src/opencode/resources/vcs/__init__.py": "abcb3cb291e4a2d846ccda54be7ec502bd72cec9d86826d29034127e828746b3",
"src/opencode/resources/vcs/branch.py": "d5c92150a4fba999411afb728425d5688236a41ad24597116e8b03933540ff18",
"src/opencode/resources/vcs/vcs.py": "ae8ef54582102103a637bc87b9c711e9e1d15bcc11971b924d7f4d39a418a0c7",
- "src/opencode/resources/voice.py": "8718417b8a82e7f0d817361f91dc06bb555b89abcace2ddee57e9394b8f43af6",
+ "src/opencode/resources/voice.py": "e3b703b0d7cd4dd6a83ed1313c50de9bd49f0bff3a2cb8739cabd257c7fe53c3",
"src/opencode/resources/websearch.py": "b9a6b5fd8cf602193b9e46efc18413b493a540deaf45908482dd2b161fc8148c",
"src/opencode/resources/worktree.py": "aa9431902c0edd7ae626f9b98aa14775155331cf3c0106e9ff5dfcd8675f0b29",
- "src/opencode/types/__init__.py": "830b81c25ccc70ba22148541fe4d3a92543a34d6c2bc8a04ae261a08f4efbfe2",
+ "src/opencode/types/__init__.py": "a795fcb0f50eae5d75e8c9e0265761cb9e270b1968ade970464e95e0ca4f9598",
"src/opencode/types/agent_get_params.py": "be8560402450e9c9688692b61277624c480cb073b79844b3255347e256f2786c",
"src/opencode/types/agent_get_response.py": "50ae1d324ae34e602f676251b3a04af01f0232131ddbc4709fb4ec6d604f47eb",
"src/opencode/types/agent_list_params.py": "1475b5da3c35ac99d1e962a94b7be2e1259ff62b6732ce22bcd71d71da4237c1",
@@ -157,24 +158,18 @@
"src/opencode/types/config_shells_response.py": "6077455be2e1e4f8f54232fc96adca92cf0ea2cdc19bc8d155b7111a0ffad103",
"src/opencode/types/config_update_params.py": "39f05b262ed97cea315b70e5597759b4b3802fa7c2a0264e5ba8f425805f2015",
"src/opencode/types/credential_create_params.py": "81bb399cc9c4598c286ef55ca072000e7948451976c02cc54e6839ee9c8d1dd0",
- "src/opencode/types/credential_create_response.py": "817724d5e181d6a06ad4ebd9763256d07ff0e94ba235b1b62bf5e3b31c073d72",
- "src/opencode/types/credential_list_response.py": "7ae0b6d38e1947cc095e1b18db24402c51a78400b3b83ce5a629dd3ff8b57d18",
+ "src/opencode/types/credential_list_response.py": "25fabbbfbee60a325aa3deb4ecdef5e390256cd5b4e815f364674d78f36f07b9",
"src/opencode/types/credential_update_params.py": "683a39154ddb37c073db77bc9b4217603e1bd65789fb9c085c064634fe7ae8bd",
"src/opencode/types/debug/__init__.py": "73b434d0b2478eb094c964ac2628e4f242b6d6b814fa8c3c4066d356bce791fa",
"src/opencode/types/debug/location_evict_params.py": "1baa10cad69cfa9227ca0013920695062270c378c678e8d22812c8512ae3ab66",
"src/opencode/types/debug/location_list_response.py": "79343c51a9b3c11c3891311aa1d2f4990af86acde1e07c75f2a3b645c01f3ff0",
- "src/opencode/types/experimental/__init__.py": "3e9ef7d26f9fa162a9167c5808b1acf2739469284567b862dd5caf89b3ba2182",
- "src/opencode/types/experimental/persistent_pty_connect_token_response.py": "77dad53707daa7523298fd82b66f1417e15563fae7bd26c3456026bd916890c9",
+ "src/opencode/types/experimental/__init__.py": "13f1cf5a70c75c13092a96b25ae2b0ce84072320eee4c73cb199e0a0d97197aa",
"src/opencode/types/experimental/persistent_pty_create_params.py": "236d282e5270c25459a4c10c196817165825d4a566a5f1a4599d53e5ff2c988e",
- "src/opencode/types/experimental/persistent_pty_create_response.py": "b91cadc3f0b7c7edaedab695ee78ecc2b1abbb12f88facff98020e603d28a2d6",
- "src/opencode/types/experimental/persistent_pty_get_response.py": "57e03a4ee7be623ed890324043a05ee384428edbf3dc6378399c9b173554fb81",
"src/opencode/types/experimental/persistent_pty_handoff_response.py": "dc578ce4f46ebbc22bd70088289372d200f9ceabe4588879ad57bde682e94e8f",
- "src/opencode/types/experimental/persistent_pty_list_response.py": "5ff88115b0eec279109bcb3f8467d550c338b2675a354e8dab0c10dfbef49499",
+ "src/opencode/types/experimental/persistent_pty_list_response.py": "1e9b6a89f2fd9d1353faf3a425f85ddd53a2ad25ae05502e330e89ebb1c50243",
"src/opencode/types/experimental/persistent_pty_read_params.py": "6830ea39823b0eab2c0d8b1c80933fdfc144bb37997202bdd6d6b7da4a1f8623",
- "src/opencode/types/experimental/persistent_pty_read_response.py": "b5020ee4a7ac6847f195a7eac2021145e0f079b1738df4cbbdf60a5388d9612f",
- "src/opencode/types/experimental/persistent_pty_snapshot_response.py": "01f6992750c7d9d9a8bdd1869ae21b67664aa467ff326aa380fd7db08e928103",
+ "src/opencode/types/experimental/persistent_pty_read_response.py": "18a9c4b83bbfb14f6db51fa9ae63e384d453019de99c743d02085c0442d38956",
"src/opencode/types/experimental/persistent_pty_update_params.py": "533ed8468baec4147bdf171c8c262f0a0bb66de9324a333ac53bf81701400ee1",
- "src/opencode/types/experimental/persistent_pty_update_response.py": "a5c02f4c53a6848cdfd39ee3b75f506bcc5bebe3f5764a543d741055175c6f69",
"src/opencode/types/file_find_params.py": "5d84fd583375a9d552ab631395ebd09f1a21bf5f02edc3e7744e592e1eacc800",
"src/opencode/types/file_find_response.py": "f04dbb6ba494ec28677c1c7dfa435039beca44af0be54e7a2eacea752d669bfe",
"src/opencode/types/file_list_params.py": "7b30be019b2b46b83344e8605bedf47ac8f9d7870079dd630abb349e70e8dfdf",
@@ -185,6 +180,7 @@
"src/opencode/types/form_list_params.py": "bdb4b755d1a0f6749574e3440e4c911894d14f2be6522b07c3167711db0910ef",
"src/opencode/types/form_list_response.py": "e4568e238aa6fb2a6d4a737b169f3c7669f59b07605022fe2b07e276a946219b",
"src/opencode/types/generate_text_params.py": "1759b815153b451de050e10a659d4a35c2fcfa401f45c703e2538306fc26486f",
+ "src/opencode/types/generate_text_response.py": "51233c77f6297357a6da77026735324d2fce6df5374b8362ec6d75e17f78493e",
"src/opencode/types/integration/__init__.py": "4a86ae7cb6a8f39f127de1ac4093eaada8862d850e60977c64007efba650833a",
"src/opencode/types/integration/command_cancel_params.py": "8977a985ad00a36b77716679e1fbb7471b70e7057be2841f253551e653c244a0",
"src/opencode/types/integration/command_connect_params.py": "da1ca04b1c05382b9ac65ed434041917054ccdd58c0913edaebcdfc8ebe4b937",
@@ -221,17 +217,15 @@
"src/opencode/types/model_default_response.py": "55de44e56e579797c852fe33817d96e370185e43be94a5e2db9267b222bd5648",
"src/opencode/types/model_list_params.py": "e08b4c429ecabc6d22090a7661e47d147b50cbcf4852a86704f7948be7bc3aa7",
"src/opencode/types/model_list_response.py": "8b35fa6f9ef1dc633ad709d9ca8872c5840fa274bdedcc79e69b52c9d4a8ad83",
- "src/opencode/types/permission/__init__.py": "c7752acf943229a63c66031d35ee24bea1df643515995d11fef0947f97cdaf34",
+ "src/opencode/types/permission/__init__.py": "b72043e3aea3eabcbb45046369c414813975ab1c9b7d3ec9abc2776d8693fb33",
"src/opencode/types/permission/request_list_params.py": "ffbf3d9b49cace4ea4dc78a751949acd2e04fe41168c934a88442becbf3d54c7",
"src/opencode/types/permission/request_list_response.py": "4acf7bd36662d6763c9bcdd63612cd00f7b8c09741f18555ada14119ddf9eaa5",
"src/opencode/types/permission/saved_list_params.py": "29ebdf1c7f2e4afc7f23bce5477f2eb0fa7cea84730a1f6d335cf0e15d154a24",
- "src/opencode/types/permission/saved_list_response.py": "00f96b4a76f74a24284dd656cf4fd35a688f211b71c5bb2eae0682ca7e8a4ea0",
+ "src/opencode/types/permission/saved_list_response.py": "1264f338e609f6cc4aa85a437a85f8322c0c5f26412f381383a72d539c181485",
"src/opencode/types/permission/saved_update_params.py": "07625ad77994424d91372db2b2407f0704dc1a04882759edaeebfa1811fd2cdb",
- "src/opencode/types/permission/saved_update_response.py": "0bffd0f9a218de2acc683d59045e669cc8979b4925298c7784ef5d96b88d123d",
"src/opencode/types/permission_create_params.py": "8cb5a23fae19dae675023a41b34c87cbea43806a6faf54aa32219c15da115e08",
- "src/opencode/types/permission_create_response.py": "adc99dd9dda7b525653fcdd63b5cda17b402955abcd1c5c62b7828f5f9cf152c",
- "src/opencode/types/permission_get_response.py": "db3d6e99551172d5af49b518baee77bbc5dc3d39d6a7c1a2c18bbce7705cbc29",
- "src/opencode/types/permission_list_response.py": "1a8cb2133946c19c6cb0ccb86827d5e2bd5910c09ffe2b4826bc5cbfb970a49e",
+ "src/opencode/types/permission_create_response.py": "19675e78b835f59e0d426a08bfc204745eec0253bb9089fd9db9f0b9cb1076c0",
+ "src/opencode/types/permission_list_response.py": "d056cc80f9ac3c9d24000097a03832e84b689c690a0c8911f0c54c9d2560274a",
"src/opencode/types/permission_reply_params.py": "1c4823e29099ca4bf1a6363d19e283ed120c1bf02a331a5bafb927374d51ba74",
"src/opencode/types/plugin_check_params.py": "f6aa54d096800e02472aaf07fbf0695fb5304ccf160d332852e7408c748c557f",
"src/opencode/types/plugin_check_response.py": "829b2a88e73530e37ca423a7146549c081f7f4d0d7ee477546d4912d1d452987",
@@ -260,53 +254,41 @@
"src/opencode/types/reference_list_params.py": "50e4a47b39223fb0e4050f942891de626133211ea1a3a747668cc5ddf7d9e061",
"src/opencode/types/reference_list_response.py": "c894bef0432b919a193895dca66fde301ac1c399871f3b9a63b465b74db2e82b",
"src/opencode/types/rpc_call_params.py": "5e07a35dfb09fa706b5912360c15946fbd40cf3853dbb9af5ba3da13f10766db",
- "src/opencode/types/session/__init__.py": "b5fd38a1f18ef5fd0ab2459c3695421e7abbc59c1cf469f3f001adf183b92611",
+ "src/opencode/types/session/__init__.py": "49cbe5f6bd6c807f7bdf671333f53b59f6dc5ac7813f78c4bcd31bc3aeee023b",
"src/opencode/types/session/form_cancel_params.py": "abb421bda795889519748aae780bf1cf2920a906a76a16999a4852e24a1eaa18",
"src/opencode/types/session/form_create_params.py": "0f0355e363dd54a01442eca9ec10e32688de0f38b36ee7b1eb48224053f29a2b",
- "src/opencode/types/session/form_create_response.py": "241357507caa4cd7389e35733872efe36362fb2e089af496f7664034006bc2a7",
- "src/opencode/types/session/form_get_response.py": "37a9def3001ac4dfcbdf2c0a342c2e55e50790428b3ce29e14b7125015aaf9c6",
- "src/opencode/types/session/form_list_response.py": "b2e96159a416668f9330eb2957467be51bc0b504a9d5c77b0a8ebe710b4f14b7",
+ "src/opencode/types/session/form_list_response.py": "0af8e2b72afa1c75779f19b0d05b2b21a1ae6720b0c3ddc33e4e43ffd1bf7d01",
"src/opencode/types/session/form_reply_params.py": "376781129720221bb31d8a7bb37f0d0aebfb8b50ab89bdc6e00dc492f3ab7b6a",
- "src/opencode/types/session/inbox_list_response.py": "3bd40fb8db352b15974d7d835f0d010677ba4bf77bfedfb835147913013c7902",
+ "src/opencode/types/session/inbox_list_response.py": "379992565700d57685030eaa17ede454fa009d44537df8faef7869aa8120b8af",
"src/opencode/types/session/inbox_update_params.py": "650c05c77c5a5f139a6b9435dd9b6fd83c09dc23da45bb3a86f8b4af2a5b7a84",
"src/opencode/types/session/instructions/__init__.py": "3483aac0cea6dee1c889af1ef0178657ba1b9f3d9f60d7f9cbbb8ca064387df6",
- "src/opencode/types/session/instructions/entry_list_response.py": "41c5233bc8c6312aa8d7a433e60776b451cb3e397b30927a45cc3bf28b529649",
+ "src/opencode/types/session/instructions/entry_list_response.py": "d196c9110e6b1f9060fdc1788e2c192596d6d7b1731396a7b767575bb2f49a0e",
"src/opencode/types/session/instructions/entry_put_params.py": "7c5158106d40dad31128b2ed9ab5baf95447ff9b7426ccd71c99c2dee13cbf33",
- "src/opencode/types/session/message_get_response.py": "cc826560d3f0370a41f1a4302a4cd92e20a2fe6230d775d274ce1bf2a069774e",
"src/opencode/types/session/revert_stage_params.py": "7517f0d130683f228fe7a4bc07350084c4acbec6b00c232b548b61a3b3b93a55",
- "src/opencode/types/session/revert_stage_response.py": "6cacd29068d89203a5344ec2d3ef73b291e55d0d0cd73d2fb297df6cf0cd89cc",
- "src/opencode/types/session_active_response.py": "7f59a9329fd5f7f85b8a998a560eb2eee09248edb100e556c6d446382bf7201a",
+ "src/opencode/types/session_active_response.py": "389bd4f749871591f572f4ab6447750dbc715dc05397d65d82a01769e90a842a",
"src/opencode/types/session_command_params.py": "a8d1895135c4fe0f8ff076c1ae8230df9d9557da55d223d697ec56957f70b6da",
"src/opencode/types/session_compact_params.py": "2b50277e6ffa1b74c9199ff0b892ea3e083e64435e8bafb6bbfef89b2a546a37",
- "src/opencode/types/session_compact_response.py": "253a83b4f4180302d373040e8d12785ed28ac41a9fd03819744801a1183c05ee",
- "src/opencode/types/session_context_response.py": "3e38c5ef8159b21fef5eb92879e4cc61682a7407a607dd24cb5cb2ffbd1464b1",
+ "src/opencode/types/session_context_response.py": "39d1187b99334011de8470950ed1ccda1490ff196fd55230461e44d923b6292b",
"src/opencode/types/session_create_params.py": "9525aa09dc85f2954dc25c13b79d30379ff1011455bc9ef19b57fe77f694afa3",
- "src/opencode/types/session_create_response.py": "ef466d0762a6aafa1916ff4a74edc88d495a9ab28165c17dc7f2e70e2ee093fb",
"src/opencode/types/session_diff_params.py": "f3d6c3f18b35fa4f025d069a809240652f808d973b9e8d169e672619f7e2f121",
- "src/opencode/types/session_diff_response.py": "1aa2b8a8f0c195e4a959c8cc44ec401666cdf5516c7bd14ba390bae00e32f08c",
+ "src/opencode/types/session_diff_response.py": "651c83a4a53164ce5519815d42f7068d75afb2198664b70a1b90bd9b75a5f9a7",
"src/opencode/types/session_environment_params.py": "a35911520e2c0b82a4c920eef3e2ba65b0bfd3455d5420add40f9e8984668354",
"src/opencode/types/session_export_params.py": "2296b68e545564b41105df5c9a7b891c228106f2a6380bc22ced70be81b65693",
- "src/opencode/types/session_export_response.py": "00744c6540c2f52f3e60997a2481cd325d4681deb9b7aea742c4e770ac5911a6",
"src/opencode/types/session_fork_params.py": "ca7ee6b420dcaf50246609a34d407c30e717133f11db7965cd3893c1691494bc",
- "src/opencode/types/session_fork_response.py": "10916090b35ff32d0a519bb294bb97838c48e5be066ec7cd2f49f7477d213e05",
"src/opencode/types/session_generate_params.py": "2d3d6eaaa87c229c39b8caf327c78bf4d12fcb7da6619357749f97730c622102",
- "src/opencode/types/session_get_response.py": "f54d9c93c99436a4d00e3988baccc85e8acc9c2404104c30279b83c30aef57f0",
+ "src/opencode/types/session_generate_response.py": "f88ac3c0ab53644d0d5fbe6b351585e17ea0f097041f87bf67c59ffc2588f955",
"src/opencode/types/session_import__params.py": "dee2d92a313a4a06635f87020f3dfff97bab4ffed24b4757d5263444265ca3fe",
- "src/opencode/types/session_import__response.py": "bb78a3740f75253d6b29f4b2b21dd1cbc94882bdf359bacd7c3ec8f72f5df3fc",
"src/opencode/types/session_interrupt_params.py": "8a9349252a0aa18bbf7c4599d63d8d00dd624753eed7f4966365d02181718d94",
"src/opencode/types/session_list_params.py": "5dab22fbb7c461e3ba721ddc8508159b04384531d872b55384ef72bcdab58069",
"src/opencode/types/session_log_params.py": "d9442d22629ae55bbb2442d1bae0d7b86a0a5118a314a3d924416e79b96fc482",
"src/opencode/types/session_move_params.py": "3c6a91501779eb32e860358358e2c3702ac5b56583b85deedeb5cdb9ec029c35",
"src/opencode/types/session_prompt_params.py": "35bae1071c0ad9db4484af213bea46df5971761c21da8a4b74b48bf786619994",
- "src/opencode/types/session_prompt_response.py": "f1cf4e75a6c0a7a804c1c24d8ab18dce6948e04153bd923926371bce9ac37346",
"src/opencode/types/session_shell_params.py": "ed4ebb44f2ce91fe821eff9730e25478db8b0036451646a0abfe458e7b11370d",
"src/opencode/types/session_skill_params.py": "18e26d1594cef3ee145742f85b184de6219148cc551ac7fef65f890a6d42c028",
"src/opencode/types/session_stats_params.py": "b811496ff06021a0fdb13ca6c3159a1e829b2fb985cf8be9b32a2569309b0fef",
- "src/opencode/types/session_stats_response.py": "2d81a3401ea90c46eb4958000f69056fb274a748f88f707b0c5733300d8311a1",
"src/opencode/types/session_switch_agent_params.py": "8bd095ea38b570191139c65d468abcc2bde78ed565938f0a19d1fc56c6947328",
"src/opencode/types/session_switch_model_params.py": "c578e2baa2b041057338222b22519affde308abda7f1bb9e556eb534367e59bf",
"src/opencode/types/session_synthetic_params.py": "a98d79a87e192d25418fed0a13aca1591dccecd548b1c668a9a7077c7cdb191a",
- "src/opencode/types/session_synthetic_response.py": "31edcf70ec3bb0734df267d06216db92a57801560a4d9395824986ca2b9855b5",
"src/opencode/types/session_update_params.py": "d764478bc1ea540f95357053339a1f0cf42db8fa7edb903c0a6df0d945ee3719",
"src/opencode/types/session_view_params.py": "f8c3de01a8752c1cd41270155ef8ddba34519538469ecb08a493b9838e297862",
"src/opencode/types/shared/__init__.py": "7499a0013c848f831de5ca14529dc97f15a10478934cd0f53c7e50ba95da24a0",
@@ -694,6 +676,7 @@
"src/opencode/types/vcs/branch_list_params.py": "d4352cd2db7ce0145e99b8d913cf7fbe533ebf6704f96612b4b50d978b441882",
"src/opencode/types/vcs/branch_list_response.py": "f19ac95438207f47c0ab9d883778a2aa2c932a83f73508e8f1194d7c41cb44a9",
"src/opencode/types/voice_session_params.py": "483e96b275aa30d59f5b019fa6bd5ec6c7b2c363991e31104aba9c05dbbe96ae",
+ "src/opencode/types/voice_session_response.py": "72a592c99668d919780d65722d08efb8cce89976cb9c2178430ff626e088ebeb",
"src/opencode/types/websearch_providers_params.py": "cd876e52d75e5e73ee6d707fc01fbd96362eb82ecbff315f0340823a7d3a993e",
"src/opencode/types/websearch_providers_response.py": "9e51380d76e876169108cd7a2c66ca730fd1490cf0de7b498185c723f7011ee7",
"src/opencode/types/websearch_query_params.py": "b41bf364dd53a78d1af0eb252601ecaf5e35e4d38623b4403e187b6cee00b987",
@@ -707,7 +690,7 @@
"tests/api_resources/debug/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/debug/test_location.py": "8dc2ed87566aac5a4c6412b90be8ab49ad2049c5f18cfd966efb8158750fe73d",
"tests/api_resources/experimental/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
- "tests/api_resources/experimental/test_persistent_pty.py": "d21032caa52a5bc787c795b24b8b57fa6f6ee2d95465d1bc55cc9d8883c3bfea",
+ "tests/api_resources/experimental/test_persistent_pty.py": "347f9f91972bfa26476ee9582acfa4eb09d6e9305e6d0c5c67f39df96b915eb9",
"tests/api_resources/integration/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/integration/test_command.py": "7df2af237dfcb3bf7850a805fbe31004b50b55cbfab17d42c281c740d20e7986",
"tests/api_resources/integration/test_connect.py": "d322a261f25500486c483b6da8566584319880939c8866bb36d6452bebf6299c",
@@ -719,30 +702,30 @@
"tests/api_resources/migration/test_v1.py": "53cf87accc6a464ab2828563d4f3cc68aab2969b317631d7aec8bbe92a07d21e",
"tests/api_resources/permission/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/permission/test_request.py": "7777ccc174ac2b7fbf310db96b4bca9340f89e198e110b0a4dcdf1a90f61c180",
- "tests/api_resources/permission/test_saved.py": "4687f51d8817bbb0c4a0a2f04c635d4acd2efe368dde4b496dc71bdb0fccbcc0",
+ "tests/api_resources/permission/test_saved.py": "7760919e341b85cf388b6e5062696f102cbf6cf986723bd8e66d74b0748ce5ea",
"tests/api_resources/pty/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/pty/test_connect.py": "4bd50ef37c3147d86a2415671c794495911fe43b10f690566db3e9b26724d56b",
"tests/api_resources/session/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/session/instructions/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/session/instructions/test_entry.py": "685b557c00560c99590879a310a8d676c9ea3d5e43397784808605241bfa8555",
- "tests/api_resources/session/test_form.py": "4f7dc1f446d92ed3e9e45b4a758e0e8ee6a475ec9ba516852d879b0c04dc62a9",
+ "tests/api_resources/session/test_form.py": "b6a70697bf9991b02b17cfe8048a637234dad94c8a80e9cc0e441f33d88f5e43",
"tests/api_resources/session/test_inbox.py": "b36d22134a8107b5a5933b80136f00108c278643a433e542ce2cacb686d0821c",
- "tests/api_resources/session/test_message.py": "08603f6ab41651629caca7b4c6e797708dbc8a2aaf488850afb244c3b8cd07cf",
- "tests/api_resources/session/test_revert.py": "52dfdae2b0ae6e57051b1e8045bf6d7b6d632e30582f820501e28d918e502522",
+ "tests/api_resources/session/test_message.py": "098ca06a0f108049c7ff234c155576095601334297ca0ccc1fcee735d7193b82",
+ "tests/api_resources/session/test_revert.py": "c4d12c0c1d4be2183501ebf7e3e67bf8bc9ec5af0e0a6e8cdd9a087d57cb890e",
"tests/api_resources/test_agent.py": "35ab8d142fda8bf9e27baeb229bbb91531da350985436a64c0b50bade38cde10",
"tests/api_resources/test_command.py": "8be3e96e7ff2ad6750fc1085ce6d422ca8e1cda3b2443dca2de0953c8f9edee5",
"tests/api_resources/test_config.py": "3b646f5206a3143c00ad183ba3a1f7cbc0a588058e8ab9751d316609cd5da218",
- "tests/api_resources/test_credential.py": "a1165ac001d3a8754562700d12a894858f98cd30360930b6599f4fdb37cbe491",
+ "tests/api_resources/test_credential.py": "bd29b2ed147895b6f26fee737ae22ce060534400af7203e869eed686b9a16322",
"tests/api_resources/test_event.py": "dea5556b0932db3ed178262dd925e3aeeda69783ebf313de0b6c20dd2f600205",
"tests/api_resources/test_file.py": "6b62a1a08c9a76d179e5829e530986df09586373a747986667edda47f4977e33",
"tests/api_resources/test_form.py": "35fdd2d079242efe0a6c26c06198910ed89a2cc1421e5f74509dd202fd830ae5",
- "tests/api_resources/test_generate.py": "339dececa80dc825f8d866cc8f2d03cb206e16adae3c776fead97482bb0d035f",
+ "tests/api_resources/test_generate.py": "7c4b732035560457d0871957f2f686298eff06f23244f9ec3be4c69296b3576b",
"tests/api_resources/test_integration.py": "d0c6e645d30405dea9bf91f7880d1c1e60d28ede6d25e07de8438cadca501911",
"tests/api_resources/test_location.py": "40f6986acf63ed7e0e279c9df41e970405ac2474f95928f28112921d8d16f13d",
"tests/api_resources/test_mcp.py": "e99fa31e92a226799a058e9408a9b9bf3ac9290169a8a40019024aae73fe188b",
"tests/api_resources/test_message.py": "b7b4654daa42cd2f70d55fdb0f8846d9003eea21327fd6d10122d2cdf4ffe775",
"tests/api_resources/test_model.py": "afd0aa11174a17f25cb7a060597d913202359074c20676ee8b0dabed853548bd",
- "tests/api_resources/test_permission.py": "008c71ba4d2539e2be080ba8330a6c23ef67cc6e0a6c350636bf798276f04b86",
+ "tests/api_resources/test_permission.py": "d9e053899f074d8b2bb675916ffdc1ebde450fb84076622194d69be2feaae336",
"tests/api_resources/test_plugin.py": "2685fa322627066f274e068959165468524db459aa47684976f57e13f35bb05b",
"tests/api_resources/test_project.py": "c9765bd34fb9ebaad4ed81bcb96f8375377674ab5a347ac4e662f8a8aa48945f",
"tests/api_resources/test_provider.py": "c44d0ea1ce6fc310e919cd2787d673b5af8b1e841f69fa5a3b96534198a58d0d",
@@ -750,11 +733,11 @@
"tests/api_resources/test_reference.py": "82661f6cecc6f0fe44d63fd1180817b27869845578f5d71d039916ee1679b8ec",
"tests/api_resources/test_rpc.py": "1dabbd8dbdab2902c05721ea5c344bd57f4c4e5c85af15c9ff60dd650baf4907",
"tests/api_resources/test_server.py": "bf49f7693308b7ede9fd1cf2e4e555dce88ec560c6e908393020e3ebc7fac9db",
- "tests/api_resources/test_session.py": "ae59919fef6cb8cab20ce219f7fd58bbef0c34a4ad45aa01ab6404dbedc9f5cb",
+ "tests/api_resources/test_session.py": "192e3371b4044440886fa9e4fe0712894dda1684b27f2c4da8ab26ef9074afb1",
"tests/api_resources/test_shell.py": "a7351fb690081ad106b0b210a9ae1c8fe0c1a65a2e801487906f0db71b357062",
"tests/api_resources/test_skill.py": "cf22c66273943ec809067cfac8d6b9d14f71e93f862b1be615ae2c51933c34cf",
"tests/api_resources/test_vcs.py": "353ea1976920276f2360acf7e720b3c4cbec2f081c9161fea09c3765ca71b25f",
- "tests/api_resources/test_voice.py": "fcef792b9df6c94f3dae3f0d29e1a106a5d0748d1dda7a054caae8040d9f13ca",
+ "tests/api_resources/test_voice.py": "8db95fb7a02962a82459891692c404f897b10e0670e0da6d6e27f451a0e4480c",
"tests/api_resources/test_websearch.py": "21e96ecd0b6cce3c1c7b376a54e782241d6ac78b6a6efc9e8dc9ae942ba79823",
"tests/api_resources/test_worktree.py": "55ea8bfa15ea226faed5a35fd4a4b8ad0ab87a94249ef09e89a97a5da848400b",
"tests/api_resources/vcs/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
@@ -781,5 +764,5 @@
"forge_commit": "91f52a40c746e8a2ac0271d8be0b7c82cdf28798",
"generator": "ferrule-gen 0.0.0",
"lock_version": 2,
- "spec_sha256": "c3399bb9cef8f8fda2757d56ac5cdf093c508479db92ed6423e82c574c17bc90"
+ "spec_sha256": "66c15e4bc3eab33cfcafe086c6ce72b3785d201fb75f911d22569256e56b7867"
}
diff --git a/src/opencode/_wrappers.py b/src/opencode/_wrappers.py
new file mode 100644
index 0000000..adc4d03
--- /dev/null
+++ b/src/opencode/_wrappers.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from typing import Generic, TypeVar
+
+from ._models import GenericModel
+
+__all__ = ["DataWrapper"]
+
+_T = TypeVar("_T")
+
+
+class DataWrapper(GenericModel, Generic[_T]):
+ data: _T
+
+ @staticmethod
+ def _unwrapper(obj: "DataWrapper[_T]") -> _T:
+ return obj.data
diff --git a/src/opencode/resources/credential.py b/src/opencode/resources/credential.py
index 8f8c10f..91f635c 100644
--- a/src/opencode/resources/credential.py
+++ b/src/opencode/resources/credential.py
@@ -2,6 +2,8 @@
from __future__ import annotations
+from typing import Type, cast
+
import httpx
from ..types import credential_create_params, credential_update_params
@@ -15,12 +17,13 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
+from .._wrappers import DataWrapper
from .._base_client import make_request_options
from ..types.shared.credential_id import CredentialID
from ..types.shared.integration_id import IntegrationID
+from ..types.shared.credential_entry import CredentialEntry
from ..types.shared.credential_value import CredentialValue
from ..types.credential_list_response import CredentialListResponse
-from ..types.credential_create_response import CredentialCreateResponse
__all__ = ["CredentialResource", "AsyncCredentialResource"]
@@ -59,7 +62,7 @@ def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> CredentialCreateResponse:
+ ) -> CredentialEntry:
"""
Store an integration credential. It becomes the integration's active credential
unless activate is false and the integration already has one. Fails with a
@@ -87,9 +90,13 @@ def create(
credential_create_params.CredentialCreateParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[CredentialEntry]._unwrapper,
),
- cast_to=CredentialCreateResponse,
+ cast_to=cast(Type[CredentialEntry], DataWrapper[CredentialEntry]),
)
def update(
@@ -142,9 +149,13 @@ def list(
return self._get(
"/api/credential",
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[CredentialListResponse]._unwrapper,
),
- cast_to=CredentialListResponse,
+ cast_to=cast(Type[CredentialListResponse], DataWrapper[CredentialListResponse]),
)
def activate(
@@ -250,7 +261,7 @@ async def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> CredentialCreateResponse:
+ ) -> CredentialEntry:
"""
Store an integration credential. It becomes the integration's active credential
unless activate is false and the integration already has one. Fails with a
@@ -278,9 +289,13 @@ async def create(
credential_create_params.CredentialCreateParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[CredentialEntry]._unwrapper,
),
- cast_to=CredentialCreateResponse,
+ cast_to=cast(Type[CredentialEntry], DataWrapper[CredentialEntry]),
)
async def update(
@@ -333,9 +348,13 @@ async def list(
return await self._get(
"/api/credential",
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[CredentialListResponse]._unwrapper,
),
- cast_to=CredentialListResponse,
+ cast_to=cast(Type[CredentialListResponse], DataWrapper[CredentialListResponse]),
)
async def activate(
diff --git a/src/opencode/resources/experimental/persistent_pty.py b/src/opencode/resources/experimental/persistent_pty.py
index 7482f95..fc2304f 100644
--- a/src/opencode/resources/experimental/persistent_pty.py
+++ b/src/opencode/resources/experimental/persistent_pty.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Dict, Optional
+from typing import Dict, Type, Optional, cast
import httpx
@@ -16,19 +16,18 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
+from ..._wrappers import DataWrapper
from ..._base_client import make_request_options
from ...types.experimental import persistent_pty_read_params, persistent_pty_create_params, persistent_pty_update_params
from ...types.shared.pty_id import PtyID
from ...types.shared.session_id import SessionID
+from ...types.shared.persistent_pty_info import PersistentPtyInfo
+from ...types.shared.persistent_pty_snapshot import PersistentPtySnapshot
+from ...types.shared.pty_ticket_connect_token import PtyTicketConnectToken
from ...types.shared.persistent_pty_read_lines import PersistentPtyReadLines
-from ...types.experimental.persistent_pty_get_response import PersistentPtyGetResponse
from ...types.experimental.persistent_pty_list_response import PersistentPtyListResponse
from ...types.experimental.persistent_pty_read_response import PersistentPtyReadResponse
-from ...types.experimental.persistent_pty_create_response import PersistentPtyCreateResponse
-from ...types.experimental.persistent_pty_update_response import PersistentPtyUpdateResponse
from ...types.experimental.persistent_pty_handoff_response import PersistentPtyHandoffResponse
-from ...types.experimental.persistent_pty_snapshot_response import PersistentPtySnapshotResponse
-from ...types.experimental.persistent_pty_connect_token_response import PersistentPtyConnectTokenResponse
__all__ = ["PersistentPtyResource", "AsyncPersistentPtyResource"]
@@ -69,7 +68,7 @@ def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> PersistentPtyCreateResponse:
+ ) -> PersistentPtyInfo:
"""
Args:
extra_headers: Send extra headers
@@ -96,9 +95,13 @@ def create(
persistent_pty_create_params.PersistentPtyCreateParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PersistentPtyInfo]._unwrapper,
),
- cast_to=PersistentPtyCreateResponse,
+ cast_to=cast(Type[PersistentPtyInfo], DataWrapper[PersistentPtyInfo]),
)
def update(
@@ -113,7 +116,7 @@ def update(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> PersistentPtyUpdateResponse:
+ ) -> PersistentPtyInfo:
"""
Args:
extra_headers: Send extra headers
@@ -136,9 +139,13 @@ def update(
persistent_pty_update_params.PersistentPtyUpdateParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PersistentPtyInfo]._unwrapper,
),
- cast_to=PersistentPtyUpdateResponse,
+ cast_to=cast(Type[PersistentPtyInfo], DataWrapper[PersistentPtyInfo]),
)
def list(
@@ -167,9 +174,13 @@ def list(
return self._get(
path_template("/api/experimental/session/{session_id}/terminal", session_id=session_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PersistentPtyListResponse]._unwrapper,
),
- cast_to=PersistentPtyListResponse,
+ cast_to=cast(Type[PersistentPtyListResponse], DataWrapper[PersistentPtyListResponse]),
)
def connect_token(
@@ -183,7 +194,7 @@ def connect_token(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> PersistentPtyConnectTokenResponse:
+ ) -> PtyTicketConnectToken:
"""
Args:
extra_headers: Send extra headers
@@ -205,9 +216,13 @@ def connect_token(
return self._post(
path_template("/api/experimental/persistent-pty/{pty_id}/connect-token", pty_id=pty_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PtyTicketConnectToken]._unwrapper,
),
- cast_to=PersistentPtyConnectTokenResponse,
+ cast_to=cast(Type[PtyTicketConnectToken], DataWrapper[PtyTicketConnectToken]),
)
def get(
@@ -220,7 +235,7 @@ def get(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> PersistentPtyGetResponse:
+ ) -> PersistentPtyInfo:
"""
Args:
extra_headers: Send extra headers
@@ -236,9 +251,13 @@ def get(
return self._get(
path_template("/api/experimental/persistent-pty/{pty_id}", pty_id=pty_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PersistentPtyInfo]._unwrapper,
),
- cast_to=PersistentPtyGetResponse,
+ cast_to=cast(Type[PersistentPtyInfo], DataWrapper[PersistentPtyInfo]),
)
def handoff(
@@ -298,8 +317,9 @@ def read(
extra_body=extra_body,
timeout=timeout,
query=maybe_transform({"lines": lines}, persistent_pty_read_params.PersistentPtyReadParams),
+ post_parser=DataWrapper[PersistentPtyReadResponse]._unwrapper,
),
- cast_to=PersistentPtyReadResponse,
+ cast_to=cast(Type[PersistentPtyReadResponse], DataWrapper[PersistentPtyReadResponse]),
)
def remove(
@@ -363,7 +383,7 @@ def snapshot(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> PersistentPtySnapshotResponse:
+ ) -> PersistentPtySnapshot:
"""
Args:
extra_headers: Send extra headers
@@ -379,9 +399,13 @@ def snapshot(
return self._get(
path_template("/api/experimental/persistent-pty/{pty_id}/snapshot", pty_id=pty_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PersistentPtySnapshot]._unwrapper,
),
- cast_to=PersistentPtySnapshotResponse,
+ cast_to=cast(Type[PersistentPtySnapshot], DataWrapper[PersistentPtySnapshot]),
)
@@ -421,7 +445,7 @@ async def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> PersistentPtyCreateResponse:
+ ) -> PersistentPtyInfo:
"""
Args:
extra_headers: Send extra headers
@@ -448,9 +472,13 @@ async def create(
persistent_pty_create_params.PersistentPtyCreateParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PersistentPtyInfo]._unwrapper,
),
- cast_to=PersistentPtyCreateResponse,
+ cast_to=cast(Type[PersistentPtyInfo], DataWrapper[PersistentPtyInfo]),
)
async def update(
@@ -465,7 +493,7 @@ async def update(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> PersistentPtyUpdateResponse:
+ ) -> PersistentPtyInfo:
"""
Args:
extra_headers: Send extra headers
@@ -488,9 +516,13 @@ async def update(
persistent_pty_update_params.PersistentPtyUpdateParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PersistentPtyInfo]._unwrapper,
),
- cast_to=PersistentPtyUpdateResponse,
+ cast_to=cast(Type[PersistentPtyInfo], DataWrapper[PersistentPtyInfo]),
)
async def list(
@@ -519,9 +551,13 @@ async def list(
return await self._get(
path_template("/api/experimental/session/{session_id}/terminal", session_id=session_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PersistentPtyListResponse]._unwrapper,
),
- cast_to=PersistentPtyListResponse,
+ cast_to=cast(Type[PersistentPtyListResponse], DataWrapper[PersistentPtyListResponse]),
)
async def connect_token(
@@ -535,7 +571,7 @@ async def connect_token(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> PersistentPtyConnectTokenResponse:
+ ) -> PtyTicketConnectToken:
"""
Args:
extra_headers: Send extra headers
@@ -557,9 +593,13 @@ async def connect_token(
return await self._post(
path_template("/api/experimental/persistent-pty/{pty_id}/connect-token", pty_id=pty_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PtyTicketConnectToken]._unwrapper,
),
- cast_to=PersistentPtyConnectTokenResponse,
+ cast_to=cast(Type[PtyTicketConnectToken], DataWrapper[PtyTicketConnectToken]),
)
async def get(
@@ -572,7 +612,7 @@ async def get(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> PersistentPtyGetResponse:
+ ) -> PersistentPtyInfo:
"""
Args:
extra_headers: Send extra headers
@@ -588,9 +628,13 @@ async def get(
return await self._get(
path_template("/api/experimental/persistent-pty/{pty_id}", pty_id=pty_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PersistentPtyInfo]._unwrapper,
),
- cast_to=PersistentPtyGetResponse,
+ cast_to=cast(Type[PersistentPtyInfo], DataWrapper[PersistentPtyInfo]),
)
async def handoff(
@@ -650,8 +694,9 @@ async def read(
extra_body=extra_body,
timeout=timeout,
query=await async_maybe_transform({"lines": lines}, persistent_pty_read_params.PersistentPtyReadParams),
+ post_parser=DataWrapper[PersistentPtyReadResponse]._unwrapper,
),
- cast_to=PersistentPtyReadResponse,
+ cast_to=cast(Type[PersistentPtyReadResponse], DataWrapper[PersistentPtyReadResponse]),
)
async def remove(
@@ -715,7 +760,7 @@ async def snapshot(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> PersistentPtySnapshotResponse:
+ ) -> PersistentPtySnapshot:
"""
Args:
extra_headers: Send extra headers
@@ -731,9 +776,13 @@ async def snapshot(
return await self._get(
path_template("/api/experimental/persistent-pty/{pty_id}/snapshot", pty_id=pty_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PersistentPtySnapshot]._unwrapper,
),
- cast_to=PersistentPtySnapshotResponse,
+ cast_to=cast(Type[PersistentPtySnapshot], DataWrapper[PersistentPtySnapshot]),
)
diff --git a/src/opencode/resources/generate.py b/src/opencode/resources/generate.py
index e9c92fc..642032f 100644
--- a/src/opencode/resources/generate.py
+++ b/src/opencode/resources/generate.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Optional
+from typing import Type, Optional, cast
import httpx
@@ -17,9 +17,10 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
+from .._wrappers import DataWrapper
from .._base_client import make_request_options
from ..types.shared.model_ref import ModelRef
-from ..types.shared.generate_text_response import GenerateTextResponse
+from ..types.generate_text_response import GenerateTextResponse
__all__ = ["GenerateResource", "AsyncGenerateResource"]
@@ -80,9 +81,13 @@ def text(
generate_text_params.GenerateTextParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[GenerateTextResponse]._unwrapper,
),
- cast_to=GenerateTextResponse,
+ cast_to=cast(Type[GenerateTextResponse], DataWrapper[GenerateTextResponse]),
)
@@ -142,9 +147,13 @@ async def text(
generate_text_params.GenerateTextParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[GenerateTextResponse]._unwrapper,
),
- cast_to=GenerateTextResponse,
+ cast_to=cast(Type[GenerateTextResponse], DataWrapper[GenerateTextResponse]),
)
diff --git a/src/opencode/resources/permission/permission.py b/src/opencode/resources/permission/permission.py
index ddfb701..5d96856 100644
--- a/src/opencode/resources/permission/permission.py
+++ b/src/opencode/resources/permission/permission.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Dict, Optional
+from typing import Dict, Type, Optional, cast
import httpx
@@ -33,14 +33,15 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
+from ..._wrappers import DataWrapper
from ..._base_client import make_request_options
from ...types.shared.agent_id import AgentID
from ...types.shared.session_id import SessionID
from ...types.shared.permission_id import PermissionID
-from ...types.permission_get_response import PermissionGetResponse
from ...types.shared.permission_reply import PermissionReply
from ...types.permission_list_response import PermissionListResponse
from ...types.shared.permission_source import PermissionSource
+from ...types.shared.permission_request import PermissionRequest
from ...types.permission_create_response import PermissionCreateResponse
__all__ = ["PermissionResource", "AsyncPermissionResource"]
@@ -122,9 +123,13 @@ def create(
permission_create_params.PermissionCreateParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PermissionCreateResponse]._unwrapper,
),
- cast_to=PermissionCreateResponse,
+ cast_to=cast(Type[PermissionCreateResponse], DataWrapper[PermissionCreateResponse]),
)
def list(
@@ -155,9 +160,13 @@ def list(
return self._get(
path_template("/api/session/{session_id}/permission", session_id=session_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PermissionListResponse]._unwrapper,
),
- cast_to=PermissionListResponse,
+ cast_to=cast(Type[PermissionListResponse], DataWrapper[PermissionListResponse]),
)
def get(
@@ -171,7 +180,7 @@ def get(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> PermissionGetResponse:
+ ) -> PermissionRequest:
"""
Retrieve a pending permission request owned by a session.
@@ -193,9 +202,13 @@ def get(
"/api/session/{session_id}/permission/{request_id}", session_id=session_id, request_id=request_id
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PermissionRequest]._unwrapper,
),
- cast_to=PermissionGetResponse,
+ cast_to=cast(Type[PermissionRequest], DataWrapper[PermissionRequest]),
)
def reply(
@@ -323,9 +336,13 @@ async def create(
permission_create_params.PermissionCreateParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PermissionCreateResponse]._unwrapper,
),
- cast_to=PermissionCreateResponse,
+ cast_to=cast(Type[PermissionCreateResponse], DataWrapper[PermissionCreateResponse]),
)
async def list(
@@ -356,9 +373,13 @@ async def list(
return await self._get(
path_template("/api/session/{session_id}/permission", session_id=session_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PermissionListResponse]._unwrapper,
),
- cast_to=PermissionListResponse,
+ cast_to=cast(Type[PermissionListResponse], DataWrapper[PermissionListResponse]),
)
async def get(
@@ -372,7 +393,7 @@ async def get(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> PermissionGetResponse:
+ ) -> PermissionRequest:
"""
Retrieve a pending permission request owned by a session.
@@ -394,9 +415,13 @@ async def get(
"/api/session/{session_id}/permission/{request_id}", session_id=session_id, request_id=request_id
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PermissionRequest]._unwrapper,
),
- cast_to=PermissionGetResponse,
+ cast_to=cast(Type[PermissionRequest], DataWrapper[PermissionRequest]),
)
async def reply(
diff --git a/src/opencode/resources/permission/saved.py b/src/opencode/resources/permission/saved.py
index 2dd95b7..71e0582 100644
--- a/src/opencode/resources/permission/saved.py
+++ b/src/opencode/resources/permission/saved.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Optional
+from typing import Type, Optional, cast
import httpx
@@ -16,12 +16,13 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
+from ..._wrappers import DataWrapper
from ..._base_client import make_request_options
from ...types.permission import saved_list_params, saved_update_params
from ...types.shared.project_id import ProjectID
from ...types.shared.permission_saved_id import PermissionSavedID
+from ...types.shared.permission_saved_info import PermissionSavedInfo
from ...types.permission.saved_list_response import SavedListResponse
-from ...types.permission.saved_update_response import SavedUpdateResponse
__all__ = ["SavedResource", "AsyncSavedResource"]
@@ -58,7 +59,7 @@ def update(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SavedUpdateResponse:
+ ) -> PermissionSavedInfo:
"""
Change the action or resource of a saved permission. Fails with a conflict when
the project already has a saved permission with the same action and resource.
@@ -84,9 +85,13 @@ def update(
saved_update_params.SavedUpdateParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PermissionSavedInfo]._unwrapper,
),
- cast_to=SavedUpdateResponse,
+ cast_to=cast(Type[PermissionSavedInfo], DataWrapper[PermissionSavedInfo]),
)
def list(
@@ -120,8 +125,9 @@ def list(
extra_body=extra_body,
timeout=timeout,
query=maybe_transform({"project_id": project_id}, saved_list_params.SavedListParams),
+ post_parser=DataWrapper[SavedListResponse]._unwrapper,
),
- cast_to=SavedListResponse,
+ cast_to=cast(Type[SavedListResponse], DataWrapper[SavedListResponse]),
)
def remove(
@@ -191,7 +197,7 @@ async def update(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SavedUpdateResponse:
+ ) -> PermissionSavedInfo:
"""
Change the action or resource of a saved permission. Fails with a conflict when
the project already has a saved permission with the same action and resource.
@@ -217,9 +223,13 @@ async def update(
saved_update_params.SavedUpdateParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[PermissionSavedInfo]._unwrapper,
),
- cast_to=SavedUpdateResponse,
+ cast_to=cast(Type[PermissionSavedInfo], DataWrapper[PermissionSavedInfo]),
)
async def list(
@@ -253,8 +263,9 @@ async def list(
extra_body=extra_body,
timeout=timeout,
query=await async_maybe_transform({"project_id": project_id}, saved_list_params.SavedListParams),
+ post_parser=DataWrapper[SavedListResponse]._unwrapper,
),
- cast_to=SavedListResponse,
+ cast_to=cast(Type[SavedListResponse], DataWrapper[SavedListResponse]),
)
async def remove(
diff --git a/src/opencode/resources/session/form.py b/src/opencode/resources/session/form.py
index 442ae4f..60f200b 100644
--- a/src/opencode/resources/session/form.py
+++ b/src/opencode/resources/session/form.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Optional
+from typing import Type, Optional, cast
import httpx
@@ -16,15 +16,16 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
+from ..._wrappers import DataWrapper
from ..._base_client import make_request_options
from ...types.session import form_reply_params, form_cancel_params, form_create_params
from ...types.shared.form_id import FormID
+from ...types.shared.form_info import FormInfo
from ...types.shared.form_answer import FormAnswer
+from ...types.shared.form_detail import FormDetail
from ...types.shared.form_fields import FormFields
from ...types.shared.form_metadata import FormMetadata
-from ...types.session.form_get_response import FormGetResponse
from ...types.session.form_list_response import FormListResponse
-from ...types.session.form_create_response import FormCreateResponse
__all__ = ["FormResource", "AsyncFormResource"]
@@ -63,7 +64,7 @@ def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> FormCreateResponse:
+ ) -> FormInfo:
"""
Create a form for a session.
@@ -90,9 +91,13 @@ def create(
form_create_params.FormCreateParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[FormInfo]._unwrapper,
),
- cast_to=FormCreateResponse,
+ cast_to=cast(Type[FormInfo], DataWrapper[FormInfo]),
)
def list(
@@ -123,9 +128,13 @@ def list(
return self._get(
path_template("/api/session/{session_id}/form", session_id=session_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[FormListResponse]._unwrapper,
),
- cast_to=FormListResponse,
+ cast_to=cast(Type[FormListResponse], DataWrapper[FormListResponse]),
)
def cancel(
@@ -181,7 +190,7 @@ def get(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> FormGetResponse:
+ ) -> FormDetail:
"""
Retrieve a form and its current state for a session.
@@ -201,9 +210,13 @@ def get(
return self._get(
path_template("/api/session/{session_id}/form/{form_id}", session_id=session_id, form_id=form_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[FormDetail]._unwrapper,
),
- cast_to=FormGetResponse,
+ cast_to=cast(Type[FormDetail], DataWrapper[FormDetail]),
)
def reply(
@@ -280,7 +293,7 @@ async def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> FormCreateResponse:
+ ) -> FormInfo:
"""
Create a form for a session.
@@ -307,9 +320,13 @@ async def create(
form_create_params.FormCreateParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[FormInfo]._unwrapper,
),
- cast_to=FormCreateResponse,
+ cast_to=cast(Type[FormInfo], DataWrapper[FormInfo]),
)
async def list(
@@ -340,9 +357,13 @@ async def list(
return await self._get(
path_template("/api/session/{session_id}/form", session_id=session_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[FormListResponse]._unwrapper,
),
- cast_to=FormListResponse,
+ cast_to=cast(Type[FormListResponse], DataWrapper[FormListResponse]),
)
async def cancel(
@@ -398,7 +419,7 @@ async def get(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> FormGetResponse:
+ ) -> FormDetail:
"""
Retrieve a form and its current state for a session.
@@ -418,9 +439,13 @@ async def get(
return await self._get(
path_template("/api/session/{session_id}/form/{form_id}", session_id=session_id, form_id=form_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[FormDetail]._unwrapper,
),
- cast_to=FormGetResponse,
+ cast_to=cast(Type[FormDetail], DataWrapper[FormDetail]),
)
async def reply(
diff --git a/src/opencode/resources/session/inbox.py b/src/opencode/resources/session/inbox.py
index 63262b7..d79acd6 100644
--- a/src/opencode/resources/session/inbox.py
+++ b/src/opencode/resources/session/inbox.py
@@ -2,6 +2,8 @@
from __future__ import annotations
+from typing import Type, cast
+
import httpx
from ..._types import Body, Query, Headers, NoneType, NotGiven, not_given
@@ -14,6 +16,7 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
+from ..._wrappers import DataWrapper
from ..._base_client import make_request_options
from ...types.session import inbox_update_params
from ...types.shared.session_id import SessionID
@@ -112,9 +115,13 @@ def list(
return self._get(
path_template("/api/session/{session_id}/inbox", session_id=session_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[InboxListResponse]._unwrapper,
),
- cast_to=InboxListResponse,
+ cast_to=cast(Type[InboxListResponse], DataWrapper[InboxListResponse]),
)
def cancel(
@@ -244,9 +251,13 @@ async def list(
return await self._get(
path_template("/api/session/{session_id}/inbox", session_id=session_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[InboxListResponse]._unwrapper,
),
- cast_to=InboxListResponse,
+ cast_to=cast(Type[InboxListResponse], DataWrapper[InboxListResponse]),
)
async def cancel(
diff --git a/src/opencode/resources/session/instructions/entry.py b/src/opencode/resources/session/instructions/entry.py
index 5a2d133..28ae932 100644
--- a/src/opencode/resources/session/instructions/entry.py
+++ b/src/opencode/resources/session/instructions/entry.py
@@ -2,6 +2,8 @@
from __future__ import annotations
+from typing import Type, cast
+
import httpx
from ...._types import Body, Query, Headers, NoneType, NotGiven, not_given
@@ -14,6 +16,7 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
+from ...._wrappers import DataWrapper
from ...._base_client import make_request_options
from ....types.shared.session_id import SessionID
from ....types.session.instructions import entry_put_params
@@ -71,9 +74,13 @@ def list(
return self._get(
path_template("/api/experimental/session/{session_id}/instructions/entries", session_id=session_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[EntryListResponse]._unwrapper,
),
- cast_to=EntryListResponse,
+ cast_to=cast(Type[EntryListResponse], DataWrapper[EntryListResponse]),
)
def put(
@@ -207,9 +214,13 @@ async def list(
return await self._get(
path_template("/api/experimental/session/{session_id}/instructions/entries", session_id=session_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[EntryListResponse]._unwrapper,
),
- cast_to=EntryListResponse,
+ cast_to=cast(Type[EntryListResponse], DataWrapper[EntryListResponse]),
)
async def put(
diff --git a/src/opencode/resources/session/message.py b/src/opencode/resources/session/message.py
index 7920a2b..ac2496b 100644
--- a/src/opencode/resources/session/message.py
+++ b/src/opencode/resources/session/message.py
@@ -2,6 +2,8 @@
from __future__ import annotations
+from typing import Type, cast
+
import httpx
from ..._types import Body, Query, Headers, NotGiven, not_given
@@ -14,10 +16,11 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
+from ..._wrappers import DataWrapper
from ..._base_client import make_request_options
from ...types.shared.session_id import SessionID
from ...types.shared.session_message_id import SessionMessageID
-from ...types.session.message_get_response import MessageGetResponse
+from ...types.shared.session_message_info import SessionMessageInfo
__all__ = ["MessageResource", "AsyncMessageResource"]
@@ -53,7 +56,7 @@ def get(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> MessageGetResponse:
+ ) -> SessionMessageInfo:
"""
Retrieve one projected message owned by the Session.
@@ -75,9 +78,13 @@ def get(
"/api/session/{session_id}/message/{message_id}", session_id=session_id, message_id=message_id
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionMessageInfo]._unwrapper,
),
- cast_to=MessageGetResponse,
+ cast_to=cast(Type[SessionMessageInfo], DataWrapper[SessionMessageInfo]),
)
@@ -112,7 +119,7 @@ async def get(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> MessageGetResponse:
+ ) -> SessionMessageInfo:
"""
Retrieve one projected message owned by the Session.
@@ -134,9 +141,13 @@ async def get(
"/api/session/{session_id}/message/{message_id}", session_id=session_id, message_id=message_id
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionMessageInfo]._unwrapper,
),
- cast_to=MessageGetResponse,
+ cast_to=cast(Type[SessionMessageInfo], DataWrapper[SessionMessageInfo]),
)
diff --git a/src/opencode/resources/session/revert.py b/src/opencode/resources/session/revert.py
index 7ba0f4f..0a3444f 100644
--- a/src/opencode/resources/session/revert.py
+++ b/src/opencode/resources/session/revert.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Optional
+from typing import Type, Optional, cast
import httpx
@@ -16,11 +16,12 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
+from ..._wrappers import DataWrapper
from ..._base_client import make_request_options
from ...types.session import revert_stage_params
from ...types.shared.session_id import SessionID
+from ...types.shared.session_revert import SessionRevert
from ...types.shared.session_message_id import SessionMessageID
-from ...types.session.revert_stage_response import RevertStageResponse
__all__ = ["RevertResource", "AsyncRevertResource"]
@@ -125,7 +126,7 @@ def stage(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> RevertStageResponse:
+ ) -> SessionRevert:
"""
Stage or move a reversible session boundary and optionally apply its file
changes.
@@ -151,9 +152,13 @@ def stage(
revert_stage_params.RevertStageParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionRevert]._unwrapper,
),
- cast_to=RevertStageResponse,
+ cast_to=cast(Type[SessionRevert], DataWrapper[SessionRevert]),
)
@@ -257,7 +262,7 @@ async def stage(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> RevertStageResponse:
+ ) -> SessionRevert:
"""
Stage or move a reversible session boundary and optionally apply its file
changes.
@@ -283,9 +288,13 @@ async def stage(
revert_stage_params.RevertStageParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionRevert]._unwrapper,
),
- cast_to=RevertStageResponse,
+ cast_to=cast(Type[SessionRevert], DataWrapper[SessionRevert]),
)
diff --git a/src/opencode/resources/session/session.py b/src/opencode/resources/session/session.py
index ec3ab3c..eeb2ad0 100644
--- a/src/opencode/resources/session/session.py
+++ b/src/opencode/resources/session/session.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Dict, Union, Iterable, Optional
+from typing import Dict, Type, Union, Iterable, Optional, cast
from typing_extensions import Literal
import httpx
@@ -73,6 +73,7 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
+from ..._wrappers import DataWrapper
from ...pagination import SyncCursorPage, AsyncCursorPage
from ..._base_client import AsyncPaginator, make_request_options
from ...types.shared.agent_id import AgentID
@@ -90,28 +91,24 @@
AsyncInstructionsResourceWithStreamingResponse,
)
from ...types.shared.session_info import SessionInfo
-from ...types.session_get_response import SessionGetResponse
from ...types.shared.absolute_path import AbsolutePath
from ...types.shared.relative_path import RelativePath
from ...types.session_diff_response import SessionDiffResponse
-from ...types.session_fork_response import SessionForkResponse
-from ...types.session_stats_response import SessionStatsResponse
from ...types.shared.sessions_cursor import SessionsCursor
from ...types.session_active_response import SessionActiveResponse
-from ...types.session_create_response import SessionCreateResponse
-from ...types.session_export_response import SessionExportResponse
-from ...types.session_prompt_response import SessionPromptResponse
from ...types.shared.session_metadata import SessionMetadata
-from ...types.session_compact_response import SessionCompactResponse
from ...types.session_context_response import SessionContextResponse
-from ...types.session_import__response import SessionImportResponse
+from ...types.session_generate_response import SessionGenerateResponse
from ...types.shared.permission_ruleset import PermissionRuleset
+from ...types.shared.session_inbox_user import SessionInboxUser
from ...types.shared.session_message_id import SessionMessageID
-from ...types.session_synthetic_response import SessionSyntheticResponse
+from ...types.shared.session_stats_info import SessionStatsInfo
from ...types.shared.location_public_ref import LocationPublicRef
+from ...types.shared.session_transfer_data import SessionTransferData
from ...types.shared.session_inbox_delivery import SessionInboxDelivery
from ...types.shared.prompt_agent_attachment import PromptAgentAttachment
-from ...types.shared.session_generate_response import SessionGenerateResponse
+from ...types.shared.session_inbox_synthetic import SessionInboxSynthetic
+from ...types.shared.session_inbox_compaction import SessionInboxCompaction
from ...types.shared.session_interrupt_response import SessionInterruptResponse
from ...types.shared.prompt_input_file_attachment import PromptInputFileAttachment
from ...types.shared.prompt_input_skill_attachment import PromptInputSkillAttachment
@@ -176,7 +173,7 @@ def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SessionCreateResponse:
+ ) -> SessionInfo:
"""
Create a session at the requested location. A parentID creates a linked child
session at its parent's location.
@@ -206,9 +203,13 @@ def create(
session_create_params.SessionCreateParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionInfo]._unwrapper,
),
- cast_to=SessionCreateResponse,
+ cast_to=cast(Type[SessionInfo], DataWrapper[SessionInfo]),
)
def update(
@@ -341,9 +342,13 @@ def active(
return self._get(
"/api/session/active",
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionActiveResponse]._unwrapper,
),
- cast_to=SessionActiveResponse,
+ cast_to=cast(Type[SessionActiveResponse], DataWrapper[SessionActiveResponse]),
)
def background(
@@ -444,7 +449,7 @@ def compact(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SessionCompactResponse:
+ ) -> SessionInboxCompaction:
"""
Durably admit a session compaction request. Steers by default: it runs at the
next step boundary instead of waiting behind queued prompts.
@@ -470,9 +475,13 @@ def compact(
session_compact_params.SessionCompactParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionInboxCompaction]._unwrapper,
),
- cast_to=SessionCompactResponse,
+ cast_to=cast(Type[SessionInboxCompaction], DataWrapper[SessionInboxCompaction]),
)
def context(
@@ -504,9 +513,13 @@ def context(
return self._get(
path_template("/api/session/{session_id}/context", session_id=session_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionContextResponse]._unwrapper,
),
- cast_to=SessionContextResponse,
+ cast_to=cast(Type[SessionContextResponse], DataWrapper[SessionContextResponse]),
)
def diff(
@@ -566,8 +579,9 @@ def diff(
},
session_diff_params.SessionDiffParams,
),
+ post_parser=DataWrapper[SessionDiffResponse]._unwrapper,
),
- cast_to=SessionDiffResponse,
+ cast_to=cast(Type[SessionDiffResponse], DataWrapper[SessionDiffResponse]),
)
def environment(
@@ -617,7 +631,7 @@ def export(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SessionExportResponse:
+ ) -> SessionTransferData:
"""
Export a complete projected session transcript.
@@ -640,8 +654,9 @@ def export(
extra_body=extra_body,
timeout=timeout,
query=maybe_transform({"sanitize": sanitize}, session_export_params.SessionExportParams),
+ post_parser=DataWrapper[SessionTransferData]._unwrapper,
),
- cast_to=SessionExportResponse,
+ cast_to=cast(Type[SessionTransferData], DataWrapper[SessionTransferData]),
)
def fork(
@@ -655,7 +670,7 @@ def fork(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SessionForkResponse:
+ ) -> SessionInfo:
"""
Create a child session by copying projected history before a message. Omit
before to copy the full history.
@@ -675,9 +690,13 @@ def fork(
path_template("/api/session/{session_id}/fork", session_id=session_id),
body=maybe_transform({"before": before}, session_fork_params.SessionForkParams),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionInfo]._unwrapper,
),
- cast_to=SessionForkResponse,
+ cast_to=cast(Type[SessionInfo], DataWrapper[SessionInfo]),
)
def generate(
@@ -711,9 +730,13 @@ def generate(
path_template("/api/session/{session_id}/generate", session_id=session_id),
body=maybe_transform({"prompt": prompt}, session_generate_params.SessionGenerateParams),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionGenerateResponse]._unwrapper,
),
- cast_to=SessionGenerateResponse,
+ cast_to=cast(Type[SessionGenerateResponse], DataWrapper[SessionGenerateResponse]),
)
def get(
@@ -726,7 +749,7 @@ def get(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SessionGetResponse:
+ ) -> SessionInfo:
"""
Retrieve a session by ID.
@@ -744,9 +767,13 @@ def get(
return self._get(
path_template("/api/session/{session_id}", session_id=session_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionInfo]._unwrapper,
),
- cast_to=SessionGetResponse,
+ cast_to=cast(Type[SessionInfo], DataWrapper[SessionInfo]),
)
def import_(
@@ -761,7 +788,7 @@ def import_(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SessionImportResponse:
+ ) -> SessionInfo:
"""
Import a projected session transcript at the requested location. Accepts
transcripts exported by earlier releases. If parentID is supplied, the parent
@@ -787,9 +814,13 @@ def import_(
session_import__params.SessionImportParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionInfo]._unwrapper,
),
- cast_to=SessionImportResponse,
+ cast_to=cast(Type[SessionInfo], DataWrapper[SessionInfo]),
)
def interrupt(
@@ -942,7 +973,7 @@ def prompt(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SessionPromptResponse:
+ ) -> SessionInboxUser:
"""
Durably admit one session input and schedule agent-loop execution unless resume
is false.
@@ -974,9 +1005,13 @@ def prompt(
session_prompt_params.SessionPromptParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionInboxUser]._unwrapper,
),
- cast_to=SessionPromptResponse,
+ cast_to=cast(Type[SessionInboxUser], DataWrapper[SessionInboxUser]),
)
def remove(
@@ -1116,7 +1151,7 @@ def stats(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SessionStatsResponse:
+ ) -> SessionStatsInfo:
"""
Aggregate local session activity, usage, and tool reliability for a time range.
@@ -1146,8 +1181,9 @@ def stats(
},
session_stats_params.SessionStatsParams,
),
+ post_parser=DataWrapper[SessionStatsInfo]._unwrapper,
),
- cast_to=SessionStatsResponse,
+ cast_to=cast(Type[SessionStatsInfo], DataWrapper[SessionStatsInfo]),
)
def switch_agent(
@@ -1238,7 +1274,7 @@ def synthetic(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SessionSyntheticResponse:
+ ) -> SessionInboxSynthetic:
"""
Durably admit synthetic session input and schedule execution unless resume is
false.
@@ -1268,9 +1304,13 @@ def synthetic(
session_synthetic_params.SessionSyntheticParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionInboxSynthetic]._unwrapper,
),
- cast_to=SessionSyntheticResponse,
+ cast_to=cast(Type[SessionInboxSynthetic], DataWrapper[SessionInboxSynthetic]),
)
def view(
@@ -1401,7 +1441,7 @@ async def create(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SessionCreateResponse:
+ ) -> SessionInfo:
"""
Create a session at the requested location. A parentID creates a linked child
session at its parent's location.
@@ -1431,9 +1471,13 @@ async def create(
session_create_params.SessionCreateParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionInfo]._unwrapper,
),
- cast_to=SessionCreateResponse,
+ cast_to=cast(Type[SessionInfo], DataWrapper[SessionInfo]),
)
async def update(
@@ -1566,9 +1610,13 @@ async def active(
return await self._get(
"/api/session/active",
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionActiveResponse]._unwrapper,
),
- cast_to=SessionActiveResponse,
+ cast_to=cast(Type[SessionActiveResponse], DataWrapper[SessionActiveResponse]),
)
async def background(
@@ -1669,7 +1717,7 @@ async def compact(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SessionCompactResponse:
+ ) -> SessionInboxCompaction:
"""
Durably admit a session compaction request. Steers by default: it runs at the
next step boundary instead of waiting behind queued prompts.
@@ -1695,9 +1743,13 @@ async def compact(
session_compact_params.SessionCompactParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionInboxCompaction]._unwrapper,
),
- cast_to=SessionCompactResponse,
+ cast_to=cast(Type[SessionInboxCompaction], DataWrapper[SessionInboxCompaction]),
)
async def context(
@@ -1729,9 +1781,13 @@ async def context(
return await self._get(
path_template("/api/session/{session_id}/context", session_id=session_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionContextResponse]._unwrapper,
),
- cast_to=SessionContextResponse,
+ cast_to=cast(Type[SessionContextResponse], DataWrapper[SessionContextResponse]),
)
async def diff(
@@ -1791,8 +1847,9 @@ async def diff(
},
session_diff_params.SessionDiffParams,
),
+ post_parser=DataWrapper[SessionDiffResponse]._unwrapper,
),
- cast_to=SessionDiffResponse,
+ cast_to=cast(Type[SessionDiffResponse], DataWrapper[SessionDiffResponse]),
)
async def environment(
@@ -1844,7 +1901,7 @@ async def export(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SessionExportResponse:
+ ) -> SessionTransferData:
"""
Export a complete projected session transcript.
@@ -1867,8 +1924,9 @@ async def export(
extra_body=extra_body,
timeout=timeout,
query=await async_maybe_transform({"sanitize": sanitize}, session_export_params.SessionExportParams),
+ post_parser=DataWrapper[SessionTransferData]._unwrapper,
),
- cast_to=SessionExportResponse,
+ cast_to=cast(Type[SessionTransferData], DataWrapper[SessionTransferData]),
)
async def fork(
@@ -1882,7 +1940,7 @@ async def fork(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SessionForkResponse:
+ ) -> SessionInfo:
"""
Create a child session by copying projected history before a message. Omit
before to copy the full history.
@@ -1902,9 +1960,13 @@ async def fork(
path_template("/api/session/{session_id}/fork", session_id=session_id),
body=await async_maybe_transform({"before": before}, session_fork_params.SessionForkParams),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionInfo]._unwrapper,
),
- cast_to=SessionForkResponse,
+ cast_to=cast(Type[SessionInfo], DataWrapper[SessionInfo]),
)
async def generate(
@@ -1938,9 +2000,13 @@ async def generate(
path_template("/api/session/{session_id}/generate", session_id=session_id),
body=await async_maybe_transform({"prompt": prompt}, session_generate_params.SessionGenerateParams),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionGenerateResponse]._unwrapper,
),
- cast_to=SessionGenerateResponse,
+ cast_to=cast(Type[SessionGenerateResponse], DataWrapper[SessionGenerateResponse]),
)
async def get(
@@ -1953,7 +2019,7 @@ async def get(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SessionGetResponse:
+ ) -> SessionInfo:
"""
Retrieve a session by ID.
@@ -1971,9 +2037,13 @@ async def get(
return await self._get(
path_template("/api/session/{session_id}", session_id=session_id),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionInfo]._unwrapper,
),
- cast_to=SessionGetResponse,
+ cast_to=cast(Type[SessionInfo], DataWrapper[SessionInfo]),
)
async def import_(
@@ -1988,7 +2058,7 @@ async def import_(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SessionImportResponse:
+ ) -> SessionInfo:
"""
Import a projected session transcript at the requested location. Accepts
transcripts exported by earlier releases. If parentID is supplied, the parent
@@ -2014,9 +2084,13 @@ async def import_(
session_import__params.SessionImportParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionInfo]._unwrapper,
),
- cast_to=SessionImportResponse,
+ cast_to=cast(Type[SessionInfo], DataWrapper[SessionInfo]),
)
async def interrupt(
@@ -2169,7 +2243,7 @@ async def prompt(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SessionPromptResponse:
+ ) -> SessionInboxUser:
"""
Durably admit one session input and schedule agent-loop execution unless resume
is false.
@@ -2201,9 +2275,13 @@ async def prompt(
session_prompt_params.SessionPromptParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionInboxUser]._unwrapper,
),
- cast_to=SessionPromptResponse,
+ cast_to=cast(Type[SessionInboxUser], DataWrapper[SessionInboxUser]),
)
async def remove(
@@ -2343,7 +2421,7 @@ async def stats(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SessionStatsResponse:
+ ) -> SessionStatsInfo:
"""
Aggregate local session activity, usage, and tool reliability for a time range.
@@ -2373,8 +2451,9 @@ async def stats(
},
session_stats_params.SessionStatsParams,
),
+ post_parser=DataWrapper[SessionStatsInfo]._unwrapper,
),
- cast_to=SessionStatsResponse,
+ cast_to=cast(Type[SessionStatsInfo], DataWrapper[SessionStatsInfo]),
)
async def switch_agent(
@@ -2465,7 +2544,7 @@ async def synthetic(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> SessionSyntheticResponse:
+ ) -> SessionInboxSynthetic:
"""
Durably admit synthetic session input and schedule execution unless resume is
false.
@@ -2495,9 +2574,13 @@ async def synthetic(
session_synthetic_params.SessionSyntheticParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[SessionInboxSynthetic]._unwrapper,
),
- cast_to=SessionSyntheticResponse,
+ cast_to=cast(Type[SessionInboxSynthetic], DataWrapper[SessionInboxSynthetic]),
)
async def view(
diff --git a/src/opencode/resources/voice.py b/src/opencode/resources/voice.py
index 6ca15e5..1909d7b 100644
--- a/src/opencode/resources/voice.py
+++ b/src/opencode/resources/voice.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Dict, Optional
+from typing import Dict, Type, Optional, cast
import httpx
@@ -17,9 +17,10 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
+from .._wrappers import DataWrapper
from .._base_client import make_request_options
+from ..types.voice_session_response import VoiceSessionResponse
from ..types.shared.voice_transport_input import VoiceTransportInput
-from ..types.shared.voice_session_response import VoiceSessionResponse
__all__ = ["VoiceResource", "AsyncVoiceResource"]
@@ -81,9 +82,13 @@ def session(
voice_session_params.VoiceSessionParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[VoiceSessionResponse]._unwrapper,
),
- cast_to=VoiceSessionResponse,
+ cast_to=cast(Type[VoiceSessionResponse], DataWrapper[VoiceSessionResponse]),
)
@@ -144,9 +149,13 @@ async def session(
voice_session_params.VoiceSessionParams,
),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ post_parser=DataWrapper[VoiceSessionResponse]._unwrapper,
),
- cast_to=VoiceSessionResponse,
+ cast_to=cast(Type[VoiceSessionResponse], DataWrapper[VoiceSessionResponse]),
)
diff --git a/src/opencode/types/__init__.py b/src/opencode/types/__init__.py
index a596357..64ecc7d 100644
--- a/src/opencode/types/__init__.py
+++ b/src/opencode/types/__init__.py
@@ -433,7 +433,6 @@
from .plugin_list_response import PluginListResponse as PluginListResponse
from .plugin_update_params import PluginUpdateParams as PluginUpdateParams
from .provider_list_params import ProviderListParams as ProviderListParams
-from .session_get_response import SessionGetResponse as SessionGetResponse
from .session_shell_params import SessionShellParams as SessionShellParams
from .session_skill_params import SessionSkillParams as SessionSkillParams
from .session_stats_params import SessionStatsParams as SessionStatsParams
@@ -449,31 +448,27 @@
from .session_create_params import SessionCreateParams as SessionCreateParams
from .session_diff_response import SessionDiffResponse as SessionDiffResponse
from .session_export_params import SessionExportParams as SessionExportParams
-from .session_fork_response import SessionForkResponse as SessionForkResponse
from .session_prompt_params import SessionPromptParams as SessionPromptParams
from .session_update_params import SessionUpdateParams as SessionUpdateParams
from .shell_create_response import ShellCreateResponse as ShellCreateResponse
from .shell_output_response import ShellOutputResponse as ShellOutputResponse
from .config_shells_response import ConfigShellsResponse as ConfigShellsResponse
+from .generate_text_response import GenerateTextResponse as GenerateTextResponse
from .integration_get_params import IntegrationGetParams as IntegrationGetParams
from .model_default_response import ModelDefaultResponse as ModelDefaultResponse
from .provider_list_response import ProviderListResponse as ProviderListResponse
from .session_command_params import SessionCommandParams as SessionCommandParams
from .session_compact_params import SessionCompactParams as SessionCompactParams
from .session_import__params import SessionImportParams as SessionImportParams
-from .session_stats_response import SessionStatsResponse as SessionStatsResponse
+from .voice_session_response import VoiceSessionResponse as VoiceSessionResponse
from .websearch_query_params import WebsearchQueryParams as WebsearchQueryParams
from .worktree_create_params import WorktreeCreateParams as WorktreeCreateParams
from .worktree_remove_params import WorktreeRemoveParams as WorktreeRemoveParams
from .integration_list_params import IntegrationListParams as IntegrationListParams
-from .permission_get_response import PermissionGetResponse as PermissionGetResponse
from .permission_reply_params import PermissionReplyParams as PermissionReplyParams
from .reference_list_response import ReferenceListResponse as ReferenceListResponse
from .session_active_response import SessionActiveResponse as SessionActiveResponse
-from .session_create_response import SessionCreateResponse as SessionCreateResponse
-from .session_export_response import SessionExportResponse as SessionExportResponse
from .session_generate_params import SessionGenerateParams as SessionGenerateParams
-from .session_prompt_response import SessionPromptResponse as SessionPromptResponse
from .worktree_refresh_params import WorktreeRefreshParams as WorktreeRefreshParams
from .credential_create_params import CredentialCreateParams as CredentialCreateParams
from .credential_list_response import CredentialListResponse as CredentialListResponse
@@ -481,17 +476,14 @@
from .integration_get_response import IntegrationGetResponse as IntegrationGetResponse
from .permission_create_params import PermissionCreateParams as PermissionCreateParams
from .permission_list_response import PermissionListResponse as PermissionListResponse
-from .session_compact_response import SessionCompactResponse as SessionCompactResponse
from .session_context_response import SessionContextResponse as SessionContextResponse
-from .session_import__response import SessionImportResponse as SessionImportResponse
from .session_interrupt_params import SessionInterruptParams as SessionInterruptParams
from .session_synthetic_params import SessionSyntheticParams as SessionSyntheticParams
from .websearch_query_response import WebsearchQueryResponse as WebsearchQueryResponse
from .integration_list_response import IntegrationListResponse as IntegrationListResponse
-from .credential_create_response import CredentialCreateResponse as CredentialCreateResponse
+from .session_generate_response import SessionGenerateResponse as SessionGenerateResponse
from .permission_create_response import PermissionCreateResponse as PermissionCreateResponse
from .session_environment_params import SessionEnvironmentParams as SessionEnvironmentParams
-from .session_synthetic_response import SessionSyntheticResponse as SessionSyntheticResponse
from .websearch_providers_params import WebsearchProvidersParams as WebsearchProvidersParams
from .session_switch_agent_params import SessionSwitchAgentParams as SessionSwitchAgentParams
from .session_switch_model_params import SessionSwitchModelParams as SessionSwitchModelParams
diff --git a/src/opencode/types/credential_create_response.py b/src/opencode/types/credential_create_response.py
deleted file mode 100644
index 1d5817e..0000000
--- a/src/opencode/types/credential_create_response.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from .._models import BaseModel
-from .shared.credential_entry import CredentialEntry
-
-__all__ = ["CredentialCreateResponse"]
-
-
-class CredentialCreateResponse(BaseModel):
- data: CredentialEntry
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/credential_list_response.py b/src/opencode/types/credential_list_response.py
index 3a9f9ee..4c7ffbf 100644
--- a/src/opencode/types/credential_list_response.py
+++ b/src/opencode/types/credential_list_response.py
@@ -1,26 +1,10 @@
# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-from typing import TYPE_CHECKING, Dict, List
+from typing import List
+from typing_extensions import TypeAlias
-from pydantic import Field as FieldInfo
-
-from .._models import BaseModel
from .shared.credential_entry import CredentialEntry
__all__ = ["CredentialListResponse"]
-
-class CredentialListResponse(BaseModel):
- data: List[CredentialEntry]
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
+CredentialListResponse: TypeAlias = List[CredentialEntry]
diff --git a/src/opencode/types/experimental/__init__.py b/src/opencode/types/experimental/__init__.py
index e0e308f..66189fa 100644
--- a/src/opencode/types/experimental/__init__.py
+++ b/src/opencode/types/experimental/__init__.py
@@ -3,15 +3,8 @@
from __future__ import annotations
from .persistent_pty_read_params import PersistentPtyReadParams as PersistentPtyReadParams
-from .persistent_pty_get_response import PersistentPtyGetResponse as PersistentPtyGetResponse
from .persistent_pty_create_params import PersistentPtyCreateParams as PersistentPtyCreateParams
from .persistent_pty_list_response import PersistentPtyListResponse as PersistentPtyListResponse
from .persistent_pty_read_response import PersistentPtyReadResponse as PersistentPtyReadResponse
from .persistent_pty_update_params import PersistentPtyUpdateParams as PersistentPtyUpdateParams
-from .persistent_pty_create_response import PersistentPtyCreateResponse as PersistentPtyCreateResponse
-from .persistent_pty_update_response import PersistentPtyUpdateResponse as PersistentPtyUpdateResponse
from .persistent_pty_handoff_response import PersistentPtyHandoffResponse as PersistentPtyHandoffResponse
-from .persistent_pty_snapshot_response import PersistentPtySnapshotResponse as PersistentPtySnapshotResponse
-from .persistent_pty_connect_token_response import (
- PersistentPtyConnectTokenResponse as PersistentPtyConnectTokenResponse,
-)
diff --git a/src/opencode/types/experimental/persistent_pty_connect_token_response.py b/src/opencode/types/experimental/persistent_pty_connect_token_response.py
deleted file mode 100644
index 24ebe22..0000000
--- a/src/opencode/types/experimental/persistent_pty_connect_token_response.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from ..._models import BaseModel
-from ..shared.pty_ticket_connect_token import PtyTicketConnectToken
-
-__all__ = ["PersistentPtyConnectTokenResponse"]
-
-
-class PersistentPtyConnectTokenResponse(BaseModel):
- data: PtyTicketConnectToken
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/experimental/persistent_pty_create_response.py b/src/opencode/types/experimental/persistent_pty_create_response.py
deleted file mode 100644
index f9db328..0000000
--- a/src/opencode/types/experimental/persistent_pty_create_response.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from ..._models import BaseModel
-from ..shared.persistent_pty_info import PersistentPtyInfo
-
-__all__ = ["PersistentPtyCreateResponse"]
-
-
-class PersistentPtyCreateResponse(BaseModel):
- data: PersistentPtyInfo
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/experimental/persistent_pty_get_response.py b/src/opencode/types/experimental/persistent_pty_get_response.py
deleted file mode 100644
index 6ef6a7c..0000000
--- a/src/opencode/types/experimental/persistent_pty_get_response.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from ..._models import BaseModel
-from ..shared.persistent_pty_info import PersistentPtyInfo
-
-__all__ = ["PersistentPtyGetResponse"]
-
-
-class PersistentPtyGetResponse(BaseModel):
- data: PersistentPtyInfo
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/experimental/persistent_pty_list_response.py b/src/opencode/types/experimental/persistent_pty_list_response.py
index d9dc363..69553cd 100644
--- a/src/opencode/types/experimental/persistent_pty_list_response.py
+++ b/src/opencode/types/experimental/persistent_pty_list_response.py
@@ -1,26 +1,10 @@
# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-from typing import TYPE_CHECKING, Dict, List
+from typing import List
+from typing_extensions import TypeAlias
-from pydantic import Field as FieldInfo
-
-from ..._models import BaseModel
from ..shared.persistent_pty_info import PersistentPtyInfo
__all__ = ["PersistentPtyListResponse"]
-
-class PersistentPtyListResponse(BaseModel):
- data: List[PersistentPtyInfo]
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
+PersistentPtyListResponse: TypeAlias = List[PersistentPtyInfo]
diff --git a/src/opencode/types/experimental/persistent_pty_read_response.py b/src/opencode/types/experimental/persistent_pty_read_response.py
index e61396b..c8ef27f 100644
--- a/src/opencode/types/experimental/persistent_pty_read_response.py
+++ b/src/opencode/types/experimental/persistent_pty_read_response.py
@@ -1,26 +1,10 @@
# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-from typing import TYPE_CHECKING, Dict, Optional
+from typing import Union
+from typing_extensions import TypeAlias
-from pydantic import Field as FieldInfo
-
-from ..._models import BaseModel
from ..shared.persistent_pty_read_result import PersistentPtyReadResult
__all__ = ["PersistentPtyReadResponse"]
-
-class PersistentPtyReadResponse(BaseModel):
- data: Optional[PersistentPtyReadResult] = None
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
+PersistentPtyReadResponse: TypeAlias = Union[PersistentPtyReadResult, None]
diff --git a/src/opencode/types/experimental/persistent_pty_snapshot_response.py b/src/opencode/types/experimental/persistent_pty_snapshot_response.py
deleted file mode 100644
index 055a3c3..0000000
--- a/src/opencode/types/experimental/persistent_pty_snapshot_response.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from ..._models import BaseModel
-from ..shared.persistent_pty_snapshot import PersistentPtySnapshot
-
-__all__ = ["PersistentPtySnapshotResponse"]
-
-
-class PersistentPtySnapshotResponse(BaseModel):
- data: PersistentPtySnapshot
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/experimental/persistent_pty_update_response.py b/src/opencode/types/experimental/persistent_pty_update_response.py
deleted file mode 100644
index 9f81906..0000000
--- a/src/opencode/types/experimental/persistent_pty_update_response.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from ..._models import BaseModel
-from ..shared.persistent_pty_info import PersistentPtyInfo
-
-__all__ = ["PersistentPtyUpdateResponse"]
-
-
-class PersistentPtyUpdateResponse(BaseModel):
- data: PersistentPtyInfo
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_get_response.py b/src/opencode/types/generate_text_response.py
similarity index 85%
rename from src/opencode/types/session_get_response.py
rename to src/opencode/types/generate_text_response.py
index 0aa01b5..5917556 100644
--- a/src/opencode/types/session_get_response.py
+++ b/src/opencode/types/generate_text_response.py
@@ -5,13 +5,12 @@
from pydantic import Field as FieldInfo
from .._models import BaseModel
-from .shared.session_info import SessionInfo
-__all__ = ["SessionGetResponse"]
+__all__ = ["GenerateTextResponse"]
-class SessionGetResponse(BaseModel):
- data: SessionInfo
+class GenerateTextResponse(BaseModel):
+ text: str
if TYPE_CHECKING:
# Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
diff --git a/src/opencode/types/permission/__init__.py b/src/opencode/types/permission/__init__.py
index 07287d6..00a54af 100644
--- a/src/opencode/types/permission/__init__.py
+++ b/src/opencode/types/permission/__init__.py
@@ -7,4 +7,3 @@
from .saved_list_response import SavedListResponse as SavedListResponse
from .saved_update_params import SavedUpdateParams as SavedUpdateParams
from .request_list_response import RequestListResponse as RequestListResponse
-from .saved_update_response import SavedUpdateResponse as SavedUpdateResponse
diff --git a/src/opencode/types/permission/saved_list_response.py b/src/opencode/types/permission/saved_list_response.py
index 6b12737..0a3100c 100644
--- a/src/opencode/types/permission/saved_list_response.py
+++ b/src/opencode/types/permission/saved_list_response.py
@@ -1,26 +1,10 @@
# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-from typing import TYPE_CHECKING, Dict, List
+from typing import List
+from typing_extensions import TypeAlias
-from pydantic import Field as FieldInfo
-
-from ..._models import BaseModel
from ..shared.permission_saved_info import PermissionSavedInfo
__all__ = ["SavedListResponse"]
-
-class SavedListResponse(BaseModel):
- data: List[PermissionSavedInfo]
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
+SavedListResponse: TypeAlias = List[PermissionSavedInfo]
diff --git a/src/opencode/types/permission/saved_update_response.py b/src/opencode/types/permission/saved_update_response.py
deleted file mode 100644
index 46a34d3..0000000
--- a/src/opencode/types/permission/saved_update_response.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from ..._models import BaseModel
-from ..shared.permission_saved_info import PermissionSavedInfo
-
-__all__ = ["SavedUpdateResponse"]
-
-
-class SavedUpdateResponse(BaseModel):
- data: PermissionSavedInfo
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/permission_create_response.py b/src/opencode/types/permission_create_response.py
index 74c8a17..c0268cd 100644
--- a/src/opencode/types/permission_create_response.py
+++ b/src/opencode/types/permission_create_response.py
@@ -8,10 +8,10 @@
from .shared.permission_id import PermissionID
from .shared.permission_effect import PermissionEffect
-__all__ = ["PermissionCreateResponse", "PermissionCreateResponseData"]
+__all__ = ["PermissionCreateResponse"]
-class PermissionCreateResponseData(BaseModel):
+class PermissionCreateResponse(BaseModel):
id: PermissionID
effect: PermissionEffect
@@ -27,19 +27,3 @@ class PermissionCreateResponseData(BaseModel):
def __getattr__(self, attr: str) -> object: ...
else:
__pydantic_extra__: Dict[str, object]
-
-
-class PermissionCreateResponse(BaseModel):
- data: PermissionCreateResponseData
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/permission_get_response.py b/src/opencode/types/permission_get_response.py
deleted file mode 100644
index 919cc23..0000000
--- a/src/opencode/types/permission_get_response.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from .._models import BaseModel
-from .shared.permission_request import PermissionRequest
-
-__all__ = ["PermissionGetResponse"]
-
-
-class PermissionGetResponse(BaseModel):
- data: PermissionRequest
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/permission_list_response.py b/src/opencode/types/permission_list_response.py
index 7837a3a..ad174ca 100644
--- a/src/opencode/types/permission_list_response.py
+++ b/src/opencode/types/permission_list_response.py
@@ -1,26 +1,10 @@
# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-from typing import TYPE_CHECKING, Dict, List
+from typing import List
+from typing_extensions import TypeAlias
-from pydantic import Field as FieldInfo
-
-from .._models import BaseModel
from .shared.permission_request import PermissionRequest
__all__ = ["PermissionListResponse"]
-
-class PermissionListResponse(BaseModel):
- data: List[PermissionRequest]
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
+PermissionListResponse: TypeAlias = List[PermissionRequest]
diff --git a/src/opencode/types/session/__init__.py b/src/opencode/types/session/__init__.py
index 928396f..aaf96f1 100644
--- a/src/opencode/types/session/__init__.py
+++ b/src/opencode/types/session/__init__.py
@@ -2,7 +2,6 @@
from __future__ import annotations
-from .form_get_response import FormGetResponse as FormGetResponse
from .form_reply_params import FormReplyParams as FormReplyParams
from .form_cancel_params import FormCancelParams as FormCancelParams
from .form_create_params import FormCreateParams as FormCreateParams
@@ -10,6 +9,3 @@
from .inbox_list_response import InboxListResponse as InboxListResponse
from .inbox_update_params import InboxUpdateParams as InboxUpdateParams
from .revert_stage_params import RevertStageParams as RevertStageParams
-from .form_create_response import FormCreateResponse as FormCreateResponse
-from .message_get_response import MessageGetResponse as MessageGetResponse
-from .revert_stage_response import RevertStageResponse as RevertStageResponse
diff --git a/src/opencode/types/session/form_create_response.py b/src/opencode/types/session/form_create_response.py
deleted file mode 100644
index 18c6602..0000000
--- a/src/opencode/types/session/form_create_response.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from ..._models import BaseModel
-from ..shared.form_info import FormInfo
-
-__all__ = ["FormCreateResponse"]
-
-
-class FormCreateResponse(BaseModel):
- data: FormInfo
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session/form_get_response.py b/src/opencode/types/session/form_get_response.py
deleted file mode 100644
index caeec6e..0000000
--- a/src/opencode/types/session/form_get_response.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from ..._models import BaseModel
-from ..shared.form_detail import FormDetail
-
-__all__ = ["FormGetResponse"]
-
-
-class FormGetResponse(BaseModel):
- data: FormDetail
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session/form_list_response.py b/src/opencode/types/session/form_list_response.py
index 94b4594..c56a3e8 100644
--- a/src/opencode/types/session/form_list_response.py
+++ b/src/opencode/types/session/form_list_response.py
@@ -1,26 +1,10 @@
# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-from typing import TYPE_CHECKING, Dict, List
+from typing import List
+from typing_extensions import TypeAlias
-from pydantic import Field as FieldInfo
-
-from ..._models import BaseModel
from ..shared.form_info import FormInfo
__all__ = ["FormListResponse"]
-
-class FormListResponse(BaseModel):
- data: List[FormInfo]
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
+FormListResponse: TypeAlias = List[FormInfo]
diff --git a/src/opencode/types/session/inbox_list_response.py b/src/opencode/types/session/inbox_list_response.py
index 65ae759..7b6bdaf 100644
--- a/src/opencode/types/session/inbox_list_response.py
+++ b/src/opencode/types/session/inbox_list_response.py
@@ -1,26 +1,10 @@
# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-from typing import TYPE_CHECKING, Dict, List
+from typing import List
+from typing_extensions import TypeAlias
-from pydantic import Field as FieldInfo
-
-from ..._models import BaseModel
from ..shared.session_inbox_info import SessionInboxInfo
__all__ = ["InboxListResponse"]
-
-class InboxListResponse(BaseModel):
- data: List[SessionInboxInfo]
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
+InboxListResponse: TypeAlias = List[SessionInboxInfo]
diff --git a/src/opencode/types/session/instructions/entry_list_response.py b/src/opencode/types/session/instructions/entry_list_response.py
index 9572cbb..00f1915 100644
--- a/src/opencode/types/session/instructions/entry_list_response.py
+++ b/src/opencode/types/session/instructions/entry_list_response.py
@@ -1,26 +1,10 @@
# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-from typing import TYPE_CHECKING, Dict, List
+from typing import List
+from typing_extensions import TypeAlias
-from pydantic import Field as FieldInfo
-
-from ...._models import BaseModel
from ...shared.instruction_entry_info import InstructionEntryInfo
__all__ = ["EntryListResponse"]
-
-class EntryListResponse(BaseModel):
- data: List[InstructionEntryInfo]
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
+EntryListResponse: TypeAlias = List[InstructionEntryInfo]
diff --git a/src/opencode/types/session/message_get_response.py b/src/opencode/types/session/message_get_response.py
deleted file mode 100644
index e6e3994..0000000
--- a/src/opencode/types/session/message_get_response.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from ..._models import BaseModel
-from ..shared.session_message_info import SessionMessageInfo
-
-__all__ = ["MessageGetResponse"]
-
-
-class MessageGetResponse(BaseModel):
- data: SessionMessageInfo
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session/revert_stage_response.py b/src/opencode/types/session/revert_stage_response.py
deleted file mode 100644
index 33c8a28..0000000
--- a/src/opencode/types/session/revert_stage_response.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from ..._models import BaseModel
-from ..shared.session_revert import SessionRevert
-
-__all__ = ["RevertStageResponse"]
-
-
-class RevertStageResponse(BaseModel):
- data: SessionRevert
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_active_response.py b/src/opencode/types/session_active_response.py
index 9f7f142..4ed0750 100644
--- a/src/opencode/types/session_active_response.py
+++ b/src/opencode/types/session_active_response.py
@@ -1,25 +1,8 @@
# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from .._models import BaseModel
+from typing import Dict
+from typing_extensions import TypeAlias
__all__ = ["SessionActiveResponse"]
-
-class SessionActiveResponse(BaseModel):
- data: Dict[str, object]
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
+SessionActiveResponse: TypeAlias = Dict[str, object]
diff --git a/src/opencode/types/session_compact_response.py b/src/opencode/types/session_compact_response.py
deleted file mode 100644
index 95b6aaf..0000000
--- a/src/opencode/types/session_compact_response.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from .._models import BaseModel
-from .shared.session_inbox_compaction import SessionInboxCompaction
-
-__all__ = ["SessionCompactResponse"]
-
-
-class SessionCompactResponse(BaseModel):
- data: SessionInboxCompaction
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_context_response.py b/src/opencode/types/session_context_response.py
index a487414..1182574 100644
--- a/src/opencode/types/session_context_response.py
+++ b/src/opencode/types/session_context_response.py
@@ -1,26 +1,10 @@
# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-from typing import TYPE_CHECKING, Dict, List
+from typing import List
+from typing_extensions import TypeAlias
-from pydantic import Field as FieldInfo
-
-from .._models import BaseModel
from .shared.session_message_info import SessionMessageInfo
__all__ = ["SessionContextResponse"]
-
-class SessionContextResponse(BaseModel):
- data: List[SessionMessageInfo]
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
+SessionContextResponse: TypeAlias = List[SessionMessageInfo]
diff --git a/src/opencode/types/session_create_response.py b/src/opencode/types/session_create_response.py
deleted file mode 100644
index ab008e7..0000000
--- a/src/opencode/types/session_create_response.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from .._models import BaseModel
-from .shared.session_info import SessionInfo
-
-__all__ = ["SessionCreateResponse"]
-
-
-class SessionCreateResponse(BaseModel):
- data: SessionInfo
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_diff_response.py b/src/opencode/types/session_diff_response.py
index db89d3f..383fb3f 100644
--- a/src/opencode/types/session_diff_response.py
+++ b/src/opencode/types/session_diff_response.py
@@ -1,26 +1,10 @@
# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-from typing import TYPE_CHECKING, Dict, List
+from typing import List
+from typing_extensions import TypeAlias
-from pydantic import Field as FieldInfo
-
-from .._models import BaseModel
from .shared.file_diff_info import FileDiffInfo
__all__ = ["SessionDiffResponse"]
-
-class SessionDiffResponse(BaseModel):
- data: List[FileDiffInfo]
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
+SessionDiffResponse: TypeAlias = List[FileDiffInfo]
diff --git a/src/opencode/types/session_export_response.py b/src/opencode/types/session_export_response.py
deleted file mode 100644
index 923a456..0000000
--- a/src/opencode/types/session_export_response.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from .._models import BaseModel
-from .shared.session_transfer_data import SessionTransferData
-
-__all__ = ["SessionExportResponse"]
-
-
-class SessionExportResponse(BaseModel):
- data: SessionTransferData
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_fork_response.py b/src/opencode/types/session_generate_response.py
similarity index 85%
rename from src/opencode/types/session_fork_response.py
rename to src/opencode/types/session_generate_response.py
index 79b09b3..d3e47e2 100644
--- a/src/opencode/types/session_fork_response.py
+++ b/src/opencode/types/session_generate_response.py
@@ -5,13 +5,12 @@
from pydantic import Field as FieldInfo
from .._models import BaseModel
-from .shared.session_info import SessionInfo
-__all__ = ["SessionForkResponse"]
+__all__ = ["SessionGenerateResponse"]
-class SessionForkResponse(BaseModel):
- data: SessionInfo
+class SessionGenerateResponse(BaseModel):
+ text: str
if TYPE_CHECKING:
# Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
diff --git a/src/opencode/types/session_prompt_response.py b/src/opencode/types/session_prompt_response.py
deleted file mode 100644
index 0ae6175..0000000
--- a/src/opencode/types/session_prompt_response.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from .._models import BaseModel
-from .shared.session_inbox_user import SessionInboxUser
-
-__all__ = ["SessionPromptResponse"]
-
-
-class SessionPromptResponse(BaseModel):
- data: SessionInboxUser
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_stats_response.py b/src/opencode/types/session_stats_response.py
deleted file mode 100644
index 3f665c7..0000000
--- a/src/opencode/types/session_stats_response.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from .._models import BaseModel
-from .shared.session_stats_info import SessionStatsInfo
-
-__all__ = ["SessionStatsResponse"]
-
-
-class SessionStatsResponse(BaseModel):
- data: SessionStatsInfo
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_synthetic_response.py b/src/opencode/types/session_synthetic_response.py
deleted file mode 100644
index 0d346a8..0000000
--- a/src/opencode/types/session_synthetic_response.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from .._models import BaseModel
-from .shared.session_inbox_synthetic import SessionInboxSynthetic
-
-__all__ = ["SessionSyntheticResponse"]
-
-
-class SessionSyntheticResponse(BaseModel):
- data: SessionInboxSynthetic
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/session_import__response.py b/src/opencode/types/voice_session_response.py
similarity index 84%
rename from src/opencode/types/session_import__response.py
rename to src/opencode/types/voice_session_response.py
index 2dd408f..7a7300a 100644
--- a/src/opencode/types/session_import__response.py
+++ b/src/opencode/types/voice_session_response.py
@@ -5,13 +5,13 @@
from pydantic import Field as FieldInfo
from .._models import BaseModel
-from .shared.session_info import SessionInfo
+from .shared.voice_transport import VoiceTransport
-__all__ = ["SessionImportResponse"]
+__all__ = ["VoiceSessionResponse"]
-class SessionImportResponse(BaseModel):
- data: SessionInfo
+class VoiceSessionResponse(BaseModel):
+ transport: VoiceTransport
if TYPE_CHECKING:
# Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
diff --git a/tests/api_resources/experimental/test_persistent_pty.py b/tests/api_resources/experimental/test_persistent_pty.py
index 39765c0..b7759e5 100644
--- a/tests/api_resources/experimental/test_persistent_pty.py
+++ b/tests/api_resources/experimental/test_persistent_pty.py
@@ -9,15 +9,15 @@
from opencode import Opencode, AsyncOpencode
from tests.utils import assert_matches_type
+from opencode.types.shared import (
+ PersistentPtyInfo,
+ PersistentPtySnapshot,
+ PtyTicketConnectToken,
+)
from opencode.types.experimental import (
- PersistentPtyGetResponse,
PersistentPtyListResponse,
PersistentPtyReadResponse,
- PersistentPtyCreateResponse,
- PersistentPtyUpdateResponse,
PersistentPtyHandoffResponse,
- PersistentPtySnapshotResponse,
- PersistentPtyConnectTokenResponse,
)
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -34,7 +34,7 @@ def test_method_create(self, client: Opencode) -> None:
env={"foo": "string"},
title="title",
)
- assert_matches_type(PersistentPtyCreateResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
@parametrize
def test_method_create_with_all_params(self, client: Opencode) -> None:
@@ -50,7 +50,7 @@ def test_method_create_with_all_params(self, client: Opencode) -> None:
"rows": 0,
},
)
- assert_matches_type(PersistentPtyCreateResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
@parametrize
def test_raw_response_create(self, client: Opencode) -> None:
@@ -64,7 +64,7 @@ def test_raw_response_create(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = response.parse()
- assert_matches_type(PersistentPtyCreateResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
@parametrize
def test_streaming_response_create(self, client: Opencode) -> None:
@@ -78,7 +78,7 @@ def test_streaming_response_create(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = response.parse()
- assert_matches_type(PersistentPtyCreateResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -101,7 +101,7 @@ def test_method_update(self, client: Opencode) -> None:
"rows": 0,
},
)
- assert_matches_type(PersistentPtyUpdateResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
@parametrize
def test_method_update_with_all_params(self, client: Opencode) -> None:
@@ -113,7 +113,7 @@ def test_method_update_with_all_params(self, client: Opencode) -> None:
},
attachment_id="attachmentID",
)
- assert_matches_type(PersistentPtyUpdateResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
@parametrize
def test_raw_response_update(self, client: Opencode) -> None:
@@ -128,7 +128,7 @@ def test_raw_response_update(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = response.parse()
- assert_matches_type(PersistentPtyUpdateResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
@parametrize
def test_streaming_response_update(self, client: Opencode) -> None:
@@ -143,7 +143,7 @@ def test_streaming_response_update(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = response.parse()
- assert_matches_type(PersistentPtyUpdateResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -201,7 +201,7 @@ def test_method_connect_token(self, client: Opencode) -> None:
persistent_pty = client.experimental.persistent_pty.connect_token(
pty_id={},
)
- assert_matches_type(PersistentPtyConnectTokenResponse, persistent_pty, path=["response"])
+ assert_matches_type(PtyTicketConnectToken, persistent_pty, path=["response"])
@parametrize
def test_method_connect_token_with_all_params(self, client: Opencode) -> None:
@@ -209,7 +209,7 @@ def test_method_connect_token_with_all_params(self, client: Opencode) -> None:
pty_id={},
x_opencode_ticket="x-opencode-ticket",
)
- assert_matches_type(PersistentPtyConnectTokenResponse, persistent_pty, path=["response"])
+ assert_matches_type(PtyTicketConnectToken, persistent_pty, path=["response"])
@parametrize
def test_raw_response_connect_token(self, client: Opencode) -> None:
@@ -220,7 +220,7 @@ def test_raw_response_connect_token(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = response.parse()
- assert_matches_type(PersistentPtyConnectTokenResponse, persistent_pty, path=["response"])
+ assert_matches_type(PtyTicketConnectToken, persistent_pty, path=["response"])
@parametrize
def test_streaming_response_connect_token(self, client: Opencode) -> None:
@@ -231,7 +231,7 @@ def test_streaming_response_connect_token(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = response.parse()
- assert_matches_type(PersistentPtyConnectTokenResponse, persistent_pty, path=["response"])
+ assert_matches_type(PtyTicketConnectToken, persistent_pty, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -247,7 +247,7 @@ def test_method_get(self, client: Opencode) -> None:
persistent_pty = client.experimental.persistent_pty.get(
{},
)
- assert_matches_type(PersistentPtyGetResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
@parametrize
def test_raw_response_get(self, client: Opencode) -> None:
@@ -258,7 +258,7 @@ def test_raw_response_get(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = response.parse()
- assert_matches_type(PersistentPtyGetResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
@parametrize
def test_streaming_response_get(self, client: Opencode) -> None:
@@ -269,7 +269,7 @@ def test_streaming_response_get(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = response.parse()
- assert_matches_type(PersistentPtyGetResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -419,7 +419,7 @@ def test_method_snapshot(self, client: Opencode) -> None:
persistent_pty = client.experimental.persistent_pty.snapshot(
{},
)
- assert_matches_type(PersistentPtySnapshotResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtySnapshot, persistent_pty, path=["response"])
@parametrize
def test_raw_response_snapshot(self, client: Opencode) -> None:
@@ -430,7 +430,7 @@ def test_raw_response_snapshot(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = response.parse()
- assert_matches_type(PersistentPtySnapshotResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtySnapshot, persistent_pty, path=["response"])
@parametrize
def test_streaming_response_snapshot(self, client: Opencode) -> None:
@@ -441,7 +441,7 @@ def test_streaming_response_snapshot(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = response.parse()
- assert_matches_type(PersistentPtySnapshotResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtySnapshot, persistent_pty, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -466,7 +466,7 @@ async def test_method_create(self, async_client: AsyncOpencode) -> None:
env={"foo": "string"},
title="title",
)
- assert_matches_type(PersistentPtyCreateResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
@@ -482,7 +482,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpencode)
"rows": 0,
},
)
- assert_matches_type(PersistentPtyCreateResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
@parametrize
async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
@@ -496,7 +496,7 @@ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = await response.parse()
- assert_matches_type(PersistentPtyCreateResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
@parametrize
async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
@@ -510,7 +510,7 @@ async def test_streaming_response_create(self, async_client: AsyncOpencode) -> N
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = await response.parse()
- assert_matches_type(PersistentPtyCreateResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -533,7 +533,7 @@ async def test_method_update(self, async_client: AsyncOpencode) -> None:
"rows": 0,
},
)
- assert_matches_type(PersistentPtyUpdateResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
@parametrize
async def test_method_update_with_all_params(self, async_client: AsyncOpencode) -> None:
@@ -545,7 +545,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncOpencode)
},
attachment_id="attachmentID",
)
- assert_matches_type(PersistentPtyUpdateResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
@parametrize
async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
@@ -560,7 +560,7 @@ async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = await response.parse()
- assert_matches_type(PersistentPtyUpdateResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
@parametrize
async def test_streaming_response_update(self, async_client: AsyncOpencode) -> None:
@@ -575,7 +575,7 @@ async def test_streaming_response_update(self, async_client: AsyncOpencode) -> N
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = await response.parse()
- assert_matches_type(PersistentPtyUpdateResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -633,7 +633,7 @@ async def test_method_connect_token(self, async_client: AsyncOpencode) -> None:
persistent_pty = await async_client.experimental.persistent_pty.connect_token(
pty_id={},
)
- assert_matches_type(PersistentPtyConnectTokenResponse, persistent_pty, path=["response"])
+ assert_matches_type(PtyTicketConnectToken, persistent_pty, path=["response"])
@parametrize
async def test_method_connect_token_with_all_params(self, async_client: AsyncOpencode) -> None:
@@ -641,7 +641,7 @@ async def test_method_connect_token_with_all_params(self, async_client: AsyncOpe
pty_id={},
x_opencode_ticket="x-opencode-ticket",
)
- assert_matches_type(PersistentPtyConnectTokenResponse, persistent_pty, path=["response"])
+ assert_matches_type(PtyTicketConnectToken, persistent_pty, path=["response"])
@parametrize
async def test_raw_response_connect_token(self, async_client: AsyncOpencode) -> None:
@@ -652,7 +652,7 @@ async def test_raw_response_connect_token(self, async_client: AsyncOpencode) ->
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = await response.parse()
- assert_matches_type(PersistentPtyConnectTokenResponse, persistent_pty, path=["response"])
+ assert_matches_type(PtyTicketConnectToken, persistent_pty, path=["response"])
@parametrize
async def test_streaming_response_connect_token(self, async_client: AsyncOpencode) -> None:
@@ -663,7 +663,7 @@ async def test_streaming_response_connect_token(self, async_client: AsyncOpencod
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = await response.parse()
- assert_matches_type(PersistentPtyConnectTokenResponse, persistent_pty, path=["response"])
+ assert_matches_type(PtyTicketConnectToken, persistent_pty, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -679,7 +679,7 @@ async def test_method_get(self, async_client: AsyncOpencode) -> None:
persistent_pty = await async_client.experimental.persistent_pty.get(
{},
)
- assert_matches_type(PersistentPtyGetResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
@parametrize
async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
@@ -690,7 +690,7 @@ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = await response.parse()
- assert_matches_type(PersistentPtyGetResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
@parametrize
async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
@@ -701,7 +701,7 @@ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = await response.parse()
- assert_matches_type(PersistentPtyGetResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -851,7 +851,7 @@ async def test_method_snapshot(self, async_client: AsyncOpencode) -> None:
persistent_pty = await async_client.experimental.persistent_pty.snapshot(
{},
)
- assert_matches_type(PersistentPtySnapshotResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtySnapshot, persistent_pty, path=["response"])
@parametrize
async def test_raw_response_snapshot(self, async_client: AsyncOpencode) -> None:
@@ -862,7 +862,7 @@ async def test_raw_response_snapshot(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = await response.parse()
- assert_matches_type(PersistentPtySnapshotResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtySnapshot, persistent_pty, path=["response"])
@parametrize
async def test_streaming_response_snapshot(self, async_client: AsyncOpencode) -> None:
@@ -873,7 +873,7 @@ async def test_streaming_response_snapshot(self, async_client: AsyncOpencode) ->
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
persistent_pty = await response.parse()
- assert_matches_type(PersistentPtySnapshotResponse, persistent_pty, path=["response"])
+ assert_matches_type(PersistentPtySnapshot, persistent_pty, path=["response"])
assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/permission/test_saved.py b/tests/api_resources/permission/test_saved.py
index 0374523..31d6f0d 100644
--- a/tests/api_resources/permission/test_saved.py
+++ b/tests/api_resources/permission/test_saved.py
@@ -9,7 +9,8 @@
from opencode import Opencode, AsyncOpencode
from tests.utils import assert_matches_type
-from opencode.types.permission import SavedListResponse, SavedUpdateResponse
+from opencode.types.shared import PermissionSavedInfo
+from opencode.types.permission import SavedListResponse
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -22,7 +23,7 @@ def test_method_update(self, client: Opencode) -> None:
saved = client.permission.saved.update(
id={},
)
- assert_matches_type(SavedUpdateResponse, saved, path=["response"])
+ assert_matches_type(PermissionSavedInfo, saved, path=["response"])
@parametrize
def test_method_update_with_all_params(self, client: Opencode) -> None:
@@ -31,7 +32,7 @@ def test_method_update_with_all_params(self, client: Opencode) -> None:
action="action",
resource="resource",
)
- assert_matches_type(SavedUpdateResponse, saved, path=["response"])
+ assert_matches_type(PermissionSavedInfo, saved, path=["response"])
@parametrize
def test_raw_response_update(self, client: Opencode) -> None:
@@ -42,7 +43,7 @@ def test_raw_response_update(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
saved = response.parse()
- assert_matches_type(SavedUpdateResponse, saved, path=["response"])
+ assert_matches_type(PermissionSavedInfo, saved, path=["response"])
@parametrize
def test_streaming_response_update(self, client: Opencode) -> None:
@@ -53,7 +54,7 @@ def test_streaming_response_update(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
saved = response.parse()
- assert_matches_type(SavedUpdateResponse, saved, path=["response"])
+ assert_matches_type(PermissionSavedInfo, saved, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -145,7 +146,7 @@ async def test_method_update(self, async_client: AsyncOpencode) -> None:
saved = await async_client.permission.saved.update(
id={},
)
- assert_matches_type(SavedUpdateResponse, saved, path=["response"])
+ assert_matches_type(PermissionSavedInfo, saved, path=["response"])
@parametrize
async def test_method_update_with_all_params(self, async_client: AsyncOpencode) -> None:
@@ -154,7 +155,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncOpencode)
action="action",
resource="resource",
)
- assert_matches_type(SavedUpdateResponse, saved, path=["response"])
+ assert_matches_type(PermissionSavedInfo, saved, path=["response"])
@parametrize
async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
@@ -165,7 +166,7 @@ async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
saved = await response.parse()
- assert_matches_type(SavedUpdateResponse, saved, path=["response"])
+ assert_matches_type(PermissionSavedInfo, saved, path=["response"])
@parametrize
async def test_streaming_response_update(self, async_client: AsyncOpencode) -> None:
@@ -176,7 +177,7 @@ async def test_streaming_response_update(self, async_client: AsyncOpencode) -> N
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
saved = await response.parse()
- assert_matches_type(SavedUpdateResponse, saved, path=["response"])
+ assert_matches_type(PermissionSavedInfo, saved, path=["response"])
assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/session/test_form.py b/tests/api_resources/session/test_form.py
index 6157939..884db76 100644
--- a/tests/api_resources/session/test_form.py
+++ b/tests/api_resources/session/test_form.py
@@ -9,7 +9,8 @@
from opencode import Opencode, AsyncOpencode
from tests.utils import assert_matches_type
-from opencode.types.session import FormGetResponse, FormListResponse, FormCreateResponse
+from opencode.types.shared import FormInfo, FormDetail
+from opencode.types.session import FormListResponse
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -24,7 +25,7 @@ def test_method_create(self, client: Opencode) -> None:
fields={},
title="title",
)
- assert_matches_type(FormCreateResponse, form, path=["response"])
+ assert_matches_type(FormInfo, form, path=["response"])
@parametrize
def test_method_create_with_all_params(self, client: Opencode) -> None:
@@ -35,7 +36,7 @@ def test_method_create_with_all_params(self, client: Opencode) -> None:
id={},
metadata={},
)
- assert_matches_type(FormCreateResponse, form, path=["response"])
+ assert_matches_type(FormInfo, form, path=["response"])
@parametrize
def test_raw_response_create(self, client: Opencode) -> None:
@@ -48,7 +49,7 @@ def test_raw_response_create(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
form = response.parse()
- assert_matches_type(FormCreateResponse, form, path=["response"])
+ assert_matches_type(FormInfo, form, path=["response"])
@parametrize
def test_streaming_response_create(self, client: Opencode) -> None:
@@ -61,7 +62,7 @@ def test_streaming_response_create(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
form = response.parse()
- assert_matches_type(FormCreateResponse, form, path=["response"])
+ assert_matches_type(FormInfo, form, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -175,7 +176,7 @@ def test_method_get(self, client: Opencode) -> None:
form_id={},
session_id="sessionID",
)
- assert_matches_type(FormGetResponse, form, path=["response"])
+ assert_matches_type(FormDetail, form, path=["response"])
@parametrize
def test_raw_response_get(self, client: Opencode) -> None:
@@ -187,7 +188,7 @@ def test_raw_response_get(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
form = response.parse()
- assert_matches_type(FormGetResponse, form, path=["response"])
+ assert_matches_type(FormDetail, form, path=["response"])
@parametrize
def test_streaming_response_get(self, client: Opencode) -> None:
@@ -199,7 +200,7 @@ def test_streaming_response_get(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
form = response.parse()
- assert_matches_type(FormGetResponse, form, path=["response"])
+ assert_matches_type(FormDetail, form, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -283,7 +284,7 @@ async def test_method_create(self, async_client: AsyncOpencode) -> None:
fields={},
title="title",
)
- assert_matches_type(FormCreateResponse, form, path=["response"])
+ assert_matches_type(FormInfo, form, path=["response"])
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
@@ -294,7 +295,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpencode)
id={},
metadata={},
)
- assert_matches_type(FormCreateResponse, form, path=["response"])
+ assert_matches_type(FormInfo, form, path=["response"])
@parametrize
async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
@@ -307,7 +308,7 @@ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
form = await response.parse()
- assert_matches_type(FormCreateResponse, form, path=["response"])
+ assert_matches_type(FormInfo, form, path=["response"])
@parametrize
async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
@@ -320,7 +321,7 @@ async def test_streaming_response_create(self, async_client: AsyncOpencode) -> N
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
form = await response.parse()
- assert_matches_type(FormCreateResponse, form, path=["response"])
+ assert_matches_type(FormInfo, form, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -434,7 +435,7 @@ async def test_method_get(self, async_client: AsyncOpencode) -> None:
form_id={},
session_id="sessionID",
)
- assert_matches_type(FormGetResponse, form, path=["response"])
+ assert_matches_type(FormDetail, form, path=["response"])
@parametrize
async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
@@ -446,7 +447,7 @@ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
form = await response.parse()
- assert_matches_type(FormGetResponse, form, path=["response"])
+ assert_matches_type(FormDetail, form, path=["response"])
@parametrize
async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
@@ -458,7 +459,7 @@ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
form = await response.parse()
- assert_matches_type(FormGetResponse, form, path=["response"])
+ assert_matches_type(FormDetail, form, path=["response"])
assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/session/test_message.py b/tests/api_resources/session/test_message.py
index a3efc16..715e0d6 100644
--- a/tests/api_resources/session/test_message.py
+++ b/tests/api_resources/session/test_message.py
@@ -9,7 +9,7 @@
from opencode import Opencode, AsyncOpencode
from tests.utils import assert_matches_type
-from opencode.types.session import MessageGetResponse
+from opencode.types.shared import SessionMessageInfo
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -23,7 +23,7 @@ def test_method_get(self, client: Opencode) -> None:
message_id={},
session_id={},
)
- assert_matches_type(MessageGetResponse, message, path=["response"])
+ assert_matches_type(SessionMessageInfo, message, path=["response"])
@parametrize
def test_raw_response_get(self, client: Opencode) -> None:
@@ -35,7 +35,7 @@ def test_raw_response_get(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
message = response.parse()
- assert_matches_type(MessageGetResponse, message, path=["response"])
+ assert_matches_type(SessionMessageInfo, message, path=["response"])
@parametrize
def test_streaming_response_get(self, client: Opencode) -> None:
@@ -47,7 +47,7 @@ def test_streaming_response_get(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
message = response.parse()
- assert_matches_type(MessageGetResponse, message, path=["response"])
+ assert_matches_type(SessionMessageInfo, message, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -77,7 +77,7 @@ async def test_method_get(self, async_client: AsyncOpencode) -> None:
message_id={},
session_id={},
)
- assert_matches_type(MessageGetResponse, message, path=["response"])
+ assert_matches_type(SessionMessageInfo, message, path=["response"])
@parametrize
async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
@@ -89,7 +89,7 @@ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
message = await response.parse()
- assert_matches_type(MessageGetResponse, message, path=["response"])
+ assert_matches_type(SessionMessageInfo, message, path=["response"])
@parametrize
async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
@@ -101,7 +101,7 @@ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
message = await response.parse()
- assert_matches_type(MessageGetResponse, message, path=["response"])
+ assert_matches_type(SessionMessageInfo, message, path=["response"])
assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/session/test_revert.py b/tests/api_resources/session/test_revert.py
index 74e5546..6b53689 100644
--- a/tests/api_resources/session/test_revert.py
+++ b/tests/api_resources/session/test_revert.py
@@ -9,7 +9,7 @@
from opencode import Opencode, AsyncOpencode
from tests.utils import assert_matches_type
-from opencode.types.session import RevertStageResponse
+from opencode.types.shared import SessionRevert
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -99,7 +99,7 @@ def test_method_stage(self, client: Opencode) -> None:
session_id={},
message_id={},
)
- assert_matches_type(RevertStageResponse, revert, path=["response"])
+ assert_matches_type(SessionRevert, revert, path=["response"])
@parametrize
def test_method_stage_with_all_params(self, client: Opencode) -> None:
@@ -108,7 +108,7 @@ def test_method_stage_with_all_params(self, client: Opencode) -> None:
message_id={},
files=True,
)
- assert_matches_type(RevertStageResponse, revert, path=["response"])
+ assert_matches_type(SessionRevert, revert, path=["response"])
@parametrize
def test_raw_response_stage(self, client: Opencode) -> None:
@@ -120,7 +120,7 @@ def test_raw_response_stage(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
revert = response.parse()
- assert_matches_type(RevertStageResponse, revert, path=["response"])
+ assert_matches_type(SessionRevert, revert, path=["response"])
@parametrize
def test_streaming_response_stage(self, client: Opencode) -> None:
@@ -132,7 +132,7 @@ def test_streaming_response_stage(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
revert = response.parse()
- assert_matches_type(RevertStageResponse, revert, path=["response"])
+ assert_matches_type(SessionRevert, revert, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -232,7 +232,7 @@ async def test_method_stage(self, async_client: AsyncOpencode) -> None:
session_id={},
message_id={},
)
- assert_matches_type(RevertStageResponse, revert, path=["response"])
+ assert_matches_type(SessionRevert, revert, path=["response"])
@parametrize
async def test_method_stage_with_all_params(self, async_client: AsyncOpencode) -> None:
@@ -241,7 +241,7 @@ async def test_method_stage_with_all_params(self, async_client: AsyncOpencode) -
message_id={},
files=True,
)
- assert_matches_type(RevertStageResponse, revert, path=["response"])
+ assert_matches_type(SessionRevert, revert, path=["response"])
@parametrize
async def test_raw_response_stage(self, async_client: AsyncOpencode) -> None:
@@ -253,7 +253,7 @@ async def test_raw_response_stage(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
revert = await response.parse()
- assert_matches_type(RevertStageResponse, revert, path=["response"])
+ assert_matches_type(SessionRevert, revert, path=["response"])
@parametrize
async def test_streaming_response_stage(self, async_client: AsyncOpencode) -> None:
@@ -265,7 +265,7 @@ async def test_streaming_response_stage(self, async_client: AsyncOpencode) -> No
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
revert = await response.parse()
- assert_matches_type(RevertStageResponse, revert, path=["response"])
+ assert_matches_type(SessionRevert, revert, path=["response"])
assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_credential.py b/tests/api_resources/test_credential.py
index 3a27ecf..f02b178 100644
--- a/tests/api_resources/test_credential.py
+++ b/tests/api_resources/test_credential.py
@@ -9,7 +9,8 @@
from opencode import Opencode, AsyncOpencode
from tests.utils import assert_matches_type
-from opencode.types import CredentialListResponse, CredentialCreateResponse
+from opencode.types import CredentialListResponse
+from opencode.types.shared import CredentialEntry
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -23,7 +24,7 @@ def test_method_create(self, client: Opencode) -> None:
integration_id={},
value={},
)
- assert_matches_type(CredentialCreateResponse, credential, path=["response"])
+ assert_matches_type(CredentialEntry, credential, path=["response"])
@parametrize
def test_method_create_with_all_params(self, client: Opencode) -> None:
@@ -34,7 +35,7 @@ def test_method_create_with_all_params(self, client: Opencode) -> None:
id={},
label="label",
)
- assert_matches_type(CredentialCreateResponse, credential, path=["response"])
+ assert_matches_type(CredentialEntry, credential, path=["response"])
@parametrize
def test_raw_response_create(self, client: Opencode) -> None:
@@ -46,7 +47,7 @@ def test_raw_response_create(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
credential = response.parse()
- assert_matches_type(CredentialCreateResponse, credential, path=["response"])
+ assert_matches_type(CredentialEntry, credential, path=["response"])
@parametrize
def test_streaming_response_create(self, client: Opencode) -> None:
@@ -58,7 +59,7 @@ def test_streaming_response_create(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
credential = response.parse()
- assert_matches_type(CredentialCreateResponse, credential, path=["response"])
+ assert_matches_type(CredentialEntry, credential, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -217,7 +218,7 @@ async def test_method_create(self, async_client: AsyncOpencode) -> None:
integration_id={},
value={},
)
- assert_matches_type(CredentialCreateResponse, credential, path=["response"])
+ assert_matches_type(CredentialEntry, credential, path=["response"])
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
@@ -228,7 +229,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpencode)
id={},
label="label",
)
- assert_matches_type(CredentialCreateResponse, credential, path=["response"])
+ assert_matches_type(CredentialEntry, credential, path=["response"])
@parametrize
async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
@@ -240,7 +241,7 @@ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
credential = await response.parse()
- assert_matches_type(CredentialCreateResponse, credential, path=["response"])
+ assert_matches_type(CredentialEntry, credential, path=["response"])
@parametrize
async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
@@ -252,7 +253,7 @@ async def test_streaming_response_create(self, async_client: AsyncOpencode) -> N
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
credential = await response.parse()
- assert_matches_type(CredentialCreateResponse, credential, path=["response"])
+ assert_matches_type(CredentialEntry, credential, path=["response"])
assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_generate.py b/tests/api_resources/test_generate.py
index 86874db..5a18b72 100644
--- a/tests/api_resources/test_generate.py
+++ b/tests/api_resources/test_generate.py
@@ -9,7 +9,7 @@
from opencode import Opencode, AsyncOpencode
from tests.utils import assert_matches_type
-from opencode.types.shared import GenerateTextResponse
+from opencode.types import GenerateTextResponse
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
diff --git a/tests/api_resources/test_permission.py b/tests/api_resources/test_permission.py
index ba28d15..95c7d49 100644
--- a/tests/api_resources/test_permission.py
+++ b/tests/api_resources/test_permission.py
@@ -9,11 +9,8 @@
from opencode import Opencode, AsyncOpencode
from tests.utils import assert_matches_type
-from opencode.types import (
- PermissionGetResponse,
- PermissionListResponse,
- PermissionCreateResponse,
-)
+from opencode.types import PermissionListResponse, PermissionCreateResponse
+from opencode.types.shared import PermissionRequest
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -125,7 +122,7 @@ def test_method_get(self, client: Opencode) -> None:
request_id={},
session_id={},
)
- assert_matches_type(PermissionGetResponse, permission, path=["response"])
+ assert_matches_type(PermissionRequest, permission, path=["response"])
@parametrize
def test_raw_response_get(self, client: Opencode) -> None:
@@ -137,7 +134,7 @@ def test_raw_response_get(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
permission = response.parse()
- assert_matches_type(PermissionGetResponse, permission, path=["response"])
+ assert_matches_type(PermissionRequest, permission, path=["response"])
@parametrize
def test_streaming_response_get(self, client: Opencode) -> None:
@@ -149,7 +146,7 @@ def test_streaming_response_get(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
permission = response.parse()
- assert_matches_type(PermissionGetResponse, permission, path=["response"])
+ assert_matches_type(PermissionRequest, permission, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -340,7 +337,7 @@ async def test_method_get(self, async_client: AsyncOpencode) -> None:
request_id={},
session_id={},
)
- assert_matches_type(PermissionGetResponse, permission, path=["response"])
+ assert_matches_type(PermissionRequest, permission, path=["response"])
@parametrize
async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
@@ -352,7 +349,7 @@ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
permission = await response.parse()
- assert_matches_type(PermissionGetResponse, permission, path=["response"])
+ assert_matches_type(PermissionRequest, permission, path=["response"])
@parametrize
async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
@@ -364,7 +361,7 @@ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
permission = await response.parse()
- assert_matches_type(PermissionGetResponse, permission, path=["response"])
+ assert_matches_type(PermissionRequest, permission, path=["response"])
assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_session.py b/tests/api_resources/test_session.py
index 1894694..4878d39 100644
--- a/tests/api_resources/test_session.py
+++ b/tests/api_resources/test_session.py
@@ -10,22 +10,18 @@
from opencode import Opencode, AsyncOpencode
from tests.utils import assert_matches_type
from opencode.types import (
- SessionGetResponse,
SessionDiffResponse,
- SessionForkResponse,
- SessionStatsResponse,
SessionActiveResponse,
- SessionCreateResponse,
- SessionExportResponse,
- SessionImportResponse,
- SessionPromptResponse,
- SessionCompactResponse,
SessionContextResponse,
- SessionSyntheticResponse,
+ SessionGenerateResponse,
)
from opencode.types.shared import (
SessionInfo,
- SessionGenerateResponse,
+ SessionInboxUser,
+ SessionStatsInfo,
+ SessionTransferData,
+ SessionInboxSynthetic,
+ SessionInboxCompaction,
SessionInterruptResponse,
)
@@ -38,7 +34,7 @@ class TestSession:
@parametrize
def test_method_create(self, client: Opencode) -> None:
session = client.session.create()
- assert_matches_type(SessionCreateResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
def test_method_create_with_all_params(self, client: Opencode) -> None:
@@ -52,7 +48,7 @@ def test_method_create_with_all_params(self, client: Opencode) -> None:
permissions={},
title="title",
)
- assert_matches_type(SessionCreateResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
def test_raw_response_create(self, client: Opencode) -> None:
@@ -61,7 +57,7 @@ def test_raw_response_create(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionCreateResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
def test_streaming_response_create(self, client: Opencode) -> None:
@@ -70,7 +66,7 @@ def test_streaming_response_create(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionCreateResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -290,7 +286,7 @@ def test_method_compact(self, client: Opencode) -> None:
session = client.session.compact(
session_id={},
)
- assert_matches_type(SessionCompactResponse, session, path=["response"])
+ assert_matches_type(SessionInboxCompaction, session, path=["response"])
@parametrize
def test_method_compact_with_all_params(self, client: Opencode) -> None:
@@ -299,7 +295,7 @@ def test_method_compact_with_all_params(self, client: Opencode) -> None:
delivery={},
id={},
)
- assert_matches_type(SessionCompactResponse, session, path=["response"])
+ assert_matches_type(SessionInboxCompaction, session, path=["response"])
@parametrize
def test_raw_response_compact(self, client: Opencode) -> None:
@@ -310,7 +306,7 @@ def test_raw_response_compact(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionCompactResponse, session, path=["response"])
+ assert_matches_type(SessionInboxCompaction, session, path=["response"])
@parametrize
def test_streaming_response_compact(self, client: Opencode) -> None:
@@ -321,7 +317,7 @@ def test_streaming_response_compact(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionCompactResponse, session, path=["response"])
+ assert_matches_type(SessionInboxCompaction, session, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -465,7 +461,7 @@ def test_method_export(self, client: Opencode) -> None:
session = client.session.export(
session_id={},
)
- assert_matches_type(SessionExportResponse, session, path=["response"])
+ assert_matches_type(SessionTransferData, session, path=["response"])
@parametrize
def test_method_export_with_all_params(self, client: Opencode) -> None:
@@ -473,7 +469,7 @@ def test_method_export_with_all_params(self, client: Opencode) -> None:
session_id={},
sanitize=True,
)
- assert_matches_type(SessionExportResponse, session, path=["response"])
+ assert_matches_type(SessionTransferData, session, path=["response"])
@parametrize
def test_raw_response_export(self, client: Opencode) -> None:
@@ -484,7 +480,7 @@ def test_raw_response_export(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionExportResponse, session, path=["response"])
+ assert_matches_type(SessionTransferData, session, path=["response"])
@parametrize
def test_streaming_response_export(self, client: Opencode) -> None:
@@ -495,7 +491,7 @@ def test_streaming_response_export(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionExportResponse, session, path=["response"])
+ assert_matches_type(SessionTransferData, session, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -511,7 +507,7 @@ def test_method_fork(self, client: Opencode) -> None:
session = client.session.fork(
session_id={},
)
- assert_matches_type(SessionForkResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
def test_method_fork_with_all_params(self, client: Opencode) -> None:
@@ -519,7 +515,7 @@ def test_method_fork_with_all_params(self, client: Opencode) -> None:
session_id={},
before={},
)
- assert_matches_type(SessionForkResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
def test_raw_response_fork(self, client: Opencode) -> None:
@@ -530,7 +526,7 @@ def test_raw_response_fork(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionForkResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
def test_streaming_response_fork(self, client: Opencode) -> None:
@@ -541,7 +537,7 @@ def test_streaming_response_fork(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionForkResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -599,7 +595,7 @@ def test_method_get(self, client: Opencode) -> None:
session = client.session.get(
{},
)
- assert_matches_type(SessionGetResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
def test_raw_response_get(self, client: Opencode) -> None:
@@ -610,7 +606,7 @@ def test_raw_response_get(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionGetResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
def test_streaming_response_get(self, client: Opencode) -> None:
@@ -621,7 +617,7 @@ def test_streaming_response_get(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionGetResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -638,7 +634,7 @@ def test_method_import_(self, client: Opencode) -> None:
info={},
messages=[{}],
)
- assert_matches_type(SessionImportResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
def test_method_import__with_all_params(self, client: Opencode) -> None:
@@ -647,7 +643,7 @@ def test_method_import__with_all_params(self, client: Opencode) -> None:
messages=[{}],
location={},
)
- assert_matches_type(SessionImportResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
def test_raw_response_import_(self, client: Opencode) -> None:
@@ -659,7 +655,7 @@ def test_raw_response_import_(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionImportResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
def test_streaming_response_import_(self, client: Opencode) -> None:
@@ -671,7 +667,7 @@ def test_streaming_response_import_(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionImportResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -825,7 +821,7 @@ def test_method_prompt(self, client: Opencode) -> None:
session_id={},
text="text",
)
- assert_matches_type(SessionPromptResponse, session, path=["response"])
+ assert_matches_type(SessionInboxUser, session, path=["response"])
@parametrize
def test_method_prompt_with_all_params(self, client: Opencode) -> None:
@@ -840,7 +836,7 @@ def test_method_prompt_with_all_params(self, client: Opencode) -> None:
resume=True,
skills=[{}],
)
- assert_matches_type(SessionPromptResponse, session, path=["response"])
+ assert_matches_type(SessionInboxUser, session, path=["response"])
@parametrize
def test_raw_response_prompt(self, client: Opencode) -> None:
@@ -852,7 +848,7 @@ def test_raw_response_prompt(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionPromptResponse, session, path=["response"])
+ assert_matches_type(SessionInboxUser, session, path=["response"])
@parametrize
def test_streaming_response_prompt(self, client: Opencode) -> None:
@@ -864,7 +860,7 @@ def test_streaming_response_prompt(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionPromptResponse, session, path=["response"])
+ assert_matches_type(SessionInboxUser, session, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -1019,7 +1015,7 @@ def test_path_params_skill(self, client: Opencode) -> None:
@parametrize
def test_method_stats(self, client: Opencode) -> None:
session = client.session.stats()
- assert_matches_type(SessionStatsResponse, session, path=["response"])
+ assert_matches_type(SessionStatsInfo, session, path=["response"])
@parametrize
def test_method_stats_with_all_params(self, client: Opencode) -> None:
@@ -1030,7 +1026,7 @@ def test_method_stats_with_all_params(self, client: Opencode) -> None:
to=0,
tools="none",
)
- assert_matches_type(SessionStatsResponse, session, path=["response"])
+ assert_matches_type(SessionStatsInfo, session, path=["response"])
@parametrize
def test_raw_response_stats(self, client: Opencode) -> None:
@@ -1039,7 +1035,7 @@ def test_raw_response_stats(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionStatsResponse, session, path=["response"])
+ assert_matches_type(SessionStatsInfo, session, path=["response"])
@parametrize
def test_streaming_response_stats(self, client: Opencode) -> None:
@@ -1048,7 +1044,7 @@ def test_streaming_response_stats(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionStatsResponse, session, path=["response"])
+ assert_matches_type(SessionStatsInfo, session, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -1142,7 +1138,7 @@ def test_method_synthetic(self, client: Opencode) -> None:
session_id={},
text="text",
)
- assert_matches_type(SessionSyntheticResponse, session, path=["response"])
+ assert_matches_type(SessionInboxSynthetic, session, path=["response"])
@parametrize
def test_method_synthetic_with_all_params(self, client: Opencode) -> None:
@@ -1155,7 +1151,7 @@ def test_method_synthetic_with_all_params(self, client: Opencode) -> None:
metadata={"foo": {}},
resume=True,
)
- assert_matches_type(SessionSyntheticResponse, session, path=["response"])
+ assert_matches_type(SessionInboxSynthetic, session, path=["response"])
@parametrize
def test_raw_response_synthetic(self, client: Opencode) -> None:
@@ -1167,7 +1163,7 @@ def test_raw_response_synthetic(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionSyntheticResponse, session, path=["response"])
+ assert_matches_type(SessionInboxSynthetic, session, path=["response"])
@parametrize
def test_streaming_response_synthetic(self, client: Opencode) -> None:
@@ -1179,7 +1175,7 @@ def test_streaming_response_synthetic(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionSyntheticResponse, session, path=["response"])
+ assert_matches_type(SessionInboxSynthetic, session, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -1280,7 +1276,7 @@ class TestAsyncSession:
@parametrize
async def test_method_create(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.create()
- assert_matches_type(SessionCreateResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
@@ -1294,7 +1290,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpencode)
permissions={},
title="title",
)
- assert_matches_type(SessionCreateResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
@@ -1303,7 +1299,7 @@ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionCreateResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
@@ -1312,7 +1308,7 @@ async def test_streaming_response_create(self, async_client: AsyncOpencode) -> N
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionCreateResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -1532,7 +1528,7 @@ async def test_method_compact(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.compact(
session_id={},
)
- assert_matches_type(SessionCompactResponse, session, path=["response"])
+ assert_matches_type(SessionInboxCompaction, session, path=["response"])
@parametrize
async def test_method_compact_with_all_params(self, async_client: AsyncOpencode) -> None:
@@ -1541,7 +1537,7 @@ async def test_method_compact_with_all_params(self, async_client: AsyncOpencode)
delivery={},
id={},
)
- assert_matches_type(SessionCompactResponse, session, path=["response"])
+ assert_matches_type(SessionInboxCompaction, session, path=["response"])
@parametrize
async def test_raw_response_compact(self, async_client: AsyncOpencode) -> None:
@@ -1552,7 +1548,7 @@ async def test_raw_response_compact(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionCompactResponse, session, path=["response"])
+ assert_matches_type(SessionInboxCompaction, session, path=["response"])
@parametrize
async def test_streaming_response_compact(self, async_client: AsyncOpencode) -> None:
@@ -1563,7 +1559,7 @@ async def test_streaming_response_compact(self, async_client: AsyncOpencode) ->
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionCompactResponse, session, path=["response"])
+ assert_matches_type(SessionInboxCompaction, session, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -1707,7 +1703,7 @@ async def test_method_export(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.export(
session_id={},
)
- assert_matches_type(SessionExportResponse, session, path=["response"])
+ assert_matches_type(SessionTransferData, session, path=["response"])
@parametrize
async def test_method_export_with_all_params(self, async_client: AsyncOpencode) -> None:
@@ -1715,7 +1711,7 @@ async def test_method_export_with_all_params(self, async_client: AsyncOpencode)
session_id={},
sanitize=True,
)
- assert_matches_type(SessionExportResponse, session, path=["response"])
+ assert_matches_type(SessionTransferData, session, path=["response"])
@parametrize
async def test_raw_response_export(self, async_client: AsyncOpencode) -> None:
@@ -1726,7 +1722,7 @@ async def test_raw_response_export(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionExportResponse, session, path=["response"])
+ assert_matches_type(SessionTransferData, session, path=["response"])
@parametrize
async def test_streaming_response_export(self, async_client: AsyncOpencode) -> None:
@@ -1737,7 +1733,7 @@ async def test_streaming_response_export(self, async_client: AsyncOpencode) -> N
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionExportResponse, session, path=["response"])
+ assert_matches_type(SessionTransferData, session, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -1753,7 +1749,7 @@ async def test_method_fork(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.fork(
session_id={},
)
- assert_matches_type(SessionForkResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
async def test_method_fork_with_all_params(self, async_client: AsyncOpencode) -> None:
@@ -1761,7 +1757,7 @@ async def test_method_fork_with_all_params(self, async_client: AsyncOpencode) ->
session_id={},
before={},
)
- assert_matches_type(SessionForkResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
async def test_raw_response_fork(self, async_client: AsyncOpencode) -> None:
@@ -1772,7 +1768,7 @@ async def test_raw_response_fork(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionForkResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
async def test_streaming_response_fork(self, async_client: AsyncOpencode) -> None:
@@ -1783,7 +1779,7 @@ async def test_streaming_response_fork(self, async_client: AsyncOpencode) -> Non
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionForkResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -1841,7 +1837,7 @@ async def test_method_get(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.get(
{},
)
- assert_matches_type(SessionGetResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
@@ -1852,7 +1848,7 @@ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionGetResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
@@ -1863,7 +1859,7 @@ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionGetResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -1880,7 +1876,7 @@ async def test_method_import_(self, async_client: AsyncOpencode) -> None:
info={},
messages=[{}],
)
- assert_matches_type(SessionImportResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
async def test_method_import__with_all_params(self, async_client: AsyncOpencode) -> None:
@@ -1889,7 +1885,7 @@ async def test_method_import__with_all_params(self, async_client: AsyncOpencode)
messages=[{}],
location={},
)
- assert_matches_type(SessionImportResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
async def test_raw_response_import_(self, async_client: AsyncOpencode) -> None:
@@ -1901,7 +1897,7 @@ async def test_raw_response_import_(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionImportResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
async def test_streaming_response_import_(self, async_client: AsyncOpencode) -> None:
@@ -1913,7 +1909,7 @@ async def test_streaming_response_import_(self, async_client: AsyncOpencode) ->
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionImportResponse, session, path=["response"])
+ assert_matches_type(SessionInfo, session, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -2067,7 +2063,7 @@ async def test_method_prompt(self, async_client: AsyncOpencode) -> None:
session_id={},
text="text",
)
- assert_matches_type(SessionPromptResponse, session, path=["response"])
+ assert_matches_type(SessionInboxUser, session, path=["response"])
@parametrize
async def test_method_prompt_with_all_params(self, async_client: AsyncOpencode) -> None:
@@ -2082,7 +2078,7 @@ async def test_method_prompt_with_all_params(self, async_client: AsyncOpencode)
resume=True,
skills=[{}],
)
- assert_matches_type(SessionPromptResponse, session, path=["response"])
+ assert_matches_type(SessionInboxUser, session, path=["response"])
@parametrize
async def test_raw_response_prompt(self, async_client: AsyncOpencode) -> None:
@@ -2094,7 +2090,7 @@ async def test_raw_response_prompt(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionPromptResponse, session, path=["response"])
+ assert_matches_type(SessionInboxUser, session, path=["response"])
@parametrize
async def test_streaming_response_prompt(self, async_client: AsyncOpencode) -> None:
@@ -2106,7 +2102,7 @@ async def test_streaming_response_prompt(self, async_client: AsyncOpencode) -> N
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionPromptResponse, session, path=["response"])
+ assert_matches_type(SessionInboxUser, session, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -2261,7 +2257,7 @@ async def test_path_params_skill(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_stats(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.stats()
- assert_matches_type(SessionStatsResponse, session, path=["response"])
+ assert_matches_type(SessionStatsInfo, session, path=["response"])
@parametrize
async def test_method_stats_with_all_params(self, async_client: AsyncOpencode) -> None:
@@ -2272,7 +2268,7 @@ async def test_method_stats_with_all_params(self, async_client: AsyncOpencode) -
to=0,
tools="none",
)
- assert_matches_type(SessionStatsResponse, session, path=["response"])
+ assert_matches_type(SessionStatsInfo, session, path=["response"])
@parametrize
async def test_raw_response_stats(self, async_client: AsyncOpencode) -> None:
@@ -2281,7 +2277,7 @@ async def test_raw_response_stats(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionStatsResponse, session, path=["response"])
+ assert_matches_type(SessionStatsInfo, session, path=["response"])
@parametrize
async def test_streaming_response_stats(self, async_client: AsyncOpencode) -> None:
@@ -2290,7 +2286,7 @@ async def test_streaming_response_stats(self, async_client: AsyncOpencode) -> No
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionStatsResponse, session, path=["response"])
+ assert_matches_type(SessionStatsInfo, session, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -2384,7 +2380,7 @@ async def test_method_synthetic(self, async_client: AsyncOpencode) -> None:
session_id={},
text="text",
)
- assert_matches_type(SessionSyntheticResponse, session, path=["response"])
+ assert_matches_type(SessionInboxSynthetic, session, path=["response"])
@parametrize
async def test_method_synthetic_with_all_params(self, async_client: AsyncOpencode) -> None:
@@ -2397,7 +2393,7 @@ async def test_method_synthetic_with_all_params(self, async_client: AsyncOpencod
metadata={"foo": {}},
resume=True,
)
- assert_matches_type(SessionSyntheticResponse, session, path=["response"])
+ assert_matches_type(SessionInboxSynthetic, session, path=["response"])
@parametrize
async def test_raw_response_synthetic(self, async_client: AsyncOpencode) -> None:
@@ -2409,7 +2405,7 @@ async def test_raw_response_synthetic(self, async_client: AsyncOpencode) -> None
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionSyntheticResponse, session, path=["response"])
+ assert_matches_type(SessionInboxSynthetic, session, path=["response"])
@parametrize
async def test_streaming_response_synthetic(self, async_client: AsyncOpencode) -> None:
@@ -2421,7 +2417,7 @@ async def test_streaming_response_synthetic(self, async_client: AsyncOpencode) -
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionSyntheticResponse, session, path=["response"])
+ assert_matches_type(SessionInboxSynthetic, session, path=["response"])
assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_voice.py b/tests/api_resources/test_voice.py
index 74a55ed..1248a0a 100644
--- a/tests/api_resources/test_voice.py
+++ b/tests/api_resources/test_voice.py
@@ -9,7 +9,7 @@
from opencode import Opencode, AsyncOpencode
from tests.utils import assert_matches_type
-from opencode.types.shared import VoiceSessionResponse
+from opencode.types import VoiceSessionResponse
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
From b92478df464b03644c3e726fb3e6ffe629ee9393 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 16:27:10 +0530
Subject: [PATCH 09/41] Keep the client's own features in a subclass of the
generated client
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
src/opencode/__init__.py | 79 ++++++++
src/opencode/_events.py | 18 +-
src/opencode/_pty.py | 3 +-
src/opencode/_rpc.py | 5 +-
src/opencode/_server.py | 6 +-
src/opencode/lib/__init__.py | 3 +-
src/opencode/lib/_client.py | 326 +++++++++++++++++++++++++++++++++
src/opencode/lib/_ready.py | 2 +-
src/opencode/lib/_resources.py | 67 +++++++
src/opencode/lib/_support.py | 6 +-
src/opencode/testing.py | 38 ++--
tests/custom/conftest.py | 5 +-
tests/custom/test_events.py | 11 +-
tests/custom/test_rpc.py | 2 +-
14 files changed, 539 insertions(+), 32 deletions(-)
create mode 100644 src/opencode/lib/_client.py
create mode 100644 src/opencode/lib/_resources.py
diff --git a/src/opencode/__init__.py b/src/opencode/__init__.py
index 3e5b0a5..2b19f0a 100644
--- a/src/opencode/__init__.py
+++ b/src/opencode/__init__.py
@@ -100,3 +100,82 @@
except (TypeError, AttributeError):
# Some of our exported symbols are builtins which we can't set attributes for.
pass
+
+# Custom code, kept by `ferrule regenerate`: the clients with this SDK's own features (lib/_client.py)
+# and the public names of the hand-written modules.
+from . import service # noqa: E402
+from ._rpc import ( # noqa: E402
+ RpcMethod,
+ RpcClient,
+ RpcEvents,
+ rpc_event,
+ rpc_method,
+ RpcCallError,
+ RpcDefinition,
+ AsyncRpcClient,
+ AsyncRpcEvents,
+ RpcEventPayload,
+ RpcEventDefinition,
+ RpcCallInternalError,
+)
+from ._pty import PtyConnection, AsyncPtyConnection, PtyConnectionError # noqa: E402
+from ._errors import * # noqa: E402, F403
+from ._errors import __all__ as _errors_all # noqa: E402
+from ._events import ( # noqa: E402
+ EventHub,
+ Reconnect,
+ Subscription,
+ AsyncEventHub,
+ session_id_of,
+ AsyncSubscription,
+ EventOverflowError,
+ SUBSCRIBER_CAPACITY,
+)
+from ._server import OpencodeServer, ServerStartError, AsyncOpencodeServer # noqa: E402
+from ._service import Endpoint, ServiceError # noqa: E402
+from .lib._client import Opencode as Opencode, AsyncOpencode as AsyncOpencode # noqa: E402
+from .pagination import SyncCursorPage, AsyncCursorPage # noqa: E402
+from ._base_client import AsyncPaginator # noqa: E402
+
+Client = Opencode
+AsyncClient = AsyncOpencode
+
+API_VERSION = "2.1.2"
+"""The OpenCode server version this client was generated from."""
+
+__all__ += [ # noqa: PLE0605
+ *(_name for _name in _errors_all if _name != "TAGGED_ERRORS"),
+ "API_VERSION",
+ "AsyncCursorPage",
+ "AsyncEventHub",
+ "AsyncOpencodeServer",
+ "AsyncPaginator",
+ "AsyncPtyConnection",
+ "AsyncRpcClient",
+ "AsyncRpcEvents",
+ "AsyncSubscription",
+ "Endpoint",
+ "EventHub",
+ "EventOverflowError",
+ "OpencodeServer",
+ "PtyConnection",
+ "PtyConnectionError",
+ "Reconnect",
+ "RpcCallError",
+ "RpcCallInternalError",
+ "RpcClient",
+ "RpcDefinition",
+ "RpcEventDefinition",
+ "RpcEventPayload",
+ "RpcEvents",
+ "RpcMethod",
+ "SUBSCRIBER_CAPACITY",
+ "ServerStartError",
+ "ServiceError",
+ "Subscription",
+ "SyncCursorPage",
+ "rpc_event",
+ "rpc_method",
+ "service",
+ "session_id_of",
+]
diff --git a/src/opencode/_events.py b/src/opencode/_events.py
index 71a2f21..c7c00c0 100644
--- a/src/opencode/_events.py
+++ b/src/opencode/_events.py
@@ -21,8 +21,10 @@
import asyncio
import collections
+import contextlib
import logging
import random
+import socket
import threading
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast
@@ -36,7 +38,7 @@
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
from types import TracebackType
- from ._client import AsyncOpencode, Opencode
+ from .lib._client import AsyncOpencode, Opencode
from ._streaming import AsyncStream, Stream
from .types import Event, ServerConnectedEvent
@@ -136,6 +138,18 @@ def _accepts(self, event: object) -> bool:
_End = anyio.EndOfStream
+
+
+def _interrupt(stream: Stream[Event]) -> None:
+ """Unblocks a read in progress on another thread, which then ends with :class:`APIConnectionError`.
+
+ ``close`` must run on the iterating thread. Call this from any other thread first to wake it.
+ """
+ network_stream: Any = stream.response.extensions.get("network_stream")
+ sock: Any = network_stream.get_extra_info("socket") if network_stream is not None else None
+ if isinstance(sock, socket.socket):
+ with contextlib.suppress(OSError):
+ sock.shutdown(socket.SHUT_RDWR)
"""Raised by :meth:`AsyncSubscription.get` once the stream is over."""
# Completion states of one subscriber.
@@ -260,7 +274,7 @@ def _stop(self, connection: _SyncConnection) -> None:
if self._current is connection:
self._current = None
if connection.stream is not None:
- connection.stream.interrupt()
+ _interrupt(connection.stream)
def _publish(self, connection: _SyncConnection, event: Event) -> None:
for subscriber in list(connection.subscribers):
diff --git a/src/opencode/_pty.py b/src/opencode/_pty.py
index 938f475..9a1bd17 100644
--- a/src/opencode/_pty.py
+++ b/src/opencode/_pty.py
@@ -46,7 +46,8 @@
from websockets.asyncio.client import ClientConnection as AsyncWebSocket
from websockets.sync.client import ClientConnection as SyncWebSocket
- from .types import Location, PtyID
+ from .lib._support import Location
+ from .types import PtyID
__all__ = [
"AsyncPersistentPtyAttachMixin",
diff --git a/src/opencode/_rpc.py b/src/opencode/_rpc.py
index 96248ef..476b851 100644
--- a/src/opencode/_rpc.py
+++ b/src/opencode/_rpc.py
@@ -49,11 +49,12 @@ class Counter(RpcDefinition, id="counter"):
from collections.abc import Mapping
from types import TracebackType
- from ._client import AsyncOpencode, Opencode
+ from .lib._client import AsyncOpencode, Opencode
from ._events import AsyncSubscription, Subscription
from ._types import Headers
from .lib._support import Timeout
- from .types import Location, RpcEvent
+ from .lib._support import Location
+ from .types import RpcEvent
__all__ = [
"AsyncRpcClient",
diff --git a/src/opencode/_server.py b/src/opencode/_server.py
index 63d4bc2..2a193a9 100644
--- a/src/opencode/_server.py
+++ b/src/opencode/_server.py
@@ -20,7 +20,7 @@
import httpx
from typing_extensions import Self
-from ._client import AsyncOpencode, Opencode
+from .lib._client import AsyncOpencode, Opencode
from ._exceptions import OpencodeError
from .lib._support import DEFAULT_USERNAME
@@ -120,14 +120,14 @@ def running(self) -> bool:
def client(self, *, directory: str | os.PathLike[str] | None = None, timeout: Timeout | None = None) -> Opencode:
"""Returns a sync client connected to this server."""
kwargs: dict[str, Any] = {} if timeout is None else {"timeout": timeout}
- return Opencode(self.url, password=self.password, username=DEFAULT_USERNAME, directory=directory, **kwargs)
+ return Opencode(base_url=self.url, password=self.password, username=DEFAULT_USERNAME, directory=directory, **kwargs)
def async_client(
self, *, directory: str | os.PathLike[str] | None = None, timeout: Timeout | None = None
) -> AsyncOpencode:
"""Returns an async client connected to this server."""
kwargs: dict[str, Any] = {} if timeout is None else {"timeout": timeout}
- return AsyncOpencode(self.url, password=self.password, username=DEFAULT_USERNAME, directory=directory, **kwargs)
+ return AsyncOpencode(base_url=self.url, password=self.password, username=DEFAULT_USERNAME, directory=directory, **kwargs)
def _command(self) -> list[str]:
executable = shutil.which(self.binary)
diff --git a/src/opencode/lib/__init__.py b/src/opencode/lib/__init__.py
index 4c455c3..77f7e3d 100644
--- a/src/opencode/lib/__init__.py
+++ b/src/opencode/lib/__init__.py
@@ -1,5 +1,6 @@
"""Hand-written helpers layered over the generated client."""
from ._session import RunResult
+from ._support import Location
-__all__ = ["RunResult"]
+__all__ = ["Location", "RunResult"]
diff --git a/src/opencode/lib/_client.py b/src/opencode/lib/_client.py
new file mode 100644
index 0000000..0f4df0e
--- /dev/null
+++ b/src/opencode/lib/_client.py
@@ -0,0 +1,326 @@
+"""The OpenCode clients: Ferrule's generated clients with this SDK's own features.
+
+Custom code, never regenerated. ``opencode.Opencode`` and ``opencode.AsyncOpencode`` are these
+subclasses; ``opencode._client`` holds the generated ones they extend. On top of the generated
+client they add:
+
+- ``directory``: the default project directory, sent as the ``location`` of every request that
+ does not pass its own;
+- ``transport``: an ``httpx`` transport for the client's own connection pool (such as
+ :class:`opencode.testing.FakeServer`);
+- ``events`` (one shared event stream), ``rpc(Definition)`` (typed plugin RPCs), ``fake()``,
+ ``from_service()`` and ``wait_until_ready()``;
+- typed errors: a response whose body carries a known ``_tag`` raises its own class.
+"""
+
+from __future__ import annotations
+
+import os
+from typing import TYPE_CHECKING, Any, cast
+
+import httpx
+from typing_extensions import Self, override
+
+from .._client import AsyncOpencode as _AsyncOpencode, Opencode as _Opencode
+from .._compat import cached_property
+from .._errors import tagged_status_error
+from .._events import AsyncEventHub, EventHub
+from .._exceptions import APIStatusError
+from .._rpc import AsyncTypedRpcResource, TypedRpcResource
+from .._service import (
+ EnsureReason,
+ ServiceError,
+ Version,
+ async_discover,
+ async_ensure,
+ discover as discover_service,
+ ensure as ensure_service,
+)
+from .._types import NOT_GIVEN, NotGiven
+from ._resources import AsyncExperimental, AsyncPty, AsyncSession, Experimental, Pty, Session
+from ._support import DEFAULT_USERNAME
+
+if TYPE_CHECKING:
+ from collections.abc import Callable, Iterable, Mapping, Sequence
+
+ from .._models import FinalRequestOptions
+ from ..testing import FakeServer
+
+__all__ = ["FAKE_BASE_URL", "AsyncOpencode", "Opencode"]
+
+FAKE_BASE_URL = "http://opencode.fake"
+"""Base URL of clients made by ``Opencode.fake()``."""
+
+
+def _with_location(options: FinalRequestOptions, directory: str | None) -> FinalRequestOptions:
+ """Send ``directory`` as the request's ``location`` unless the call passed one."""
+ if directory is None:
+ return options
+ params = options.params
+ if isinstance(params, dict) and "location" not in params:
+ options.params = {**cast("dict[str, object]", params), "location": {"directory": directory}}
+ return options
+
+
+def _directory(value: str | os.PathLike[str] | None) -> str | None:
+ return None if value is None else os.fspath(value)
+
+
+class Opencode(_Opencode):
+ """Synchronous client for an OpenCode server.
+
+ Takes the generated client's arguments (``base_url``, ``username``, ``password``, ``timeout``,
+ ``max_retries``, ``default_headers``, ``default_query``, ``http_client``) and:
+
+ Args:
+ directory: Default project directory, sent as the ``location`` of every request that does not
+ pass its own.
+ transport: An ``httpx`` transport for the client's own connection pool, such as
+ :class:`opencode.testing.FakeServer`. Cannot be combined with ``http_client``.
+
+ Example::
+
+ from opencode import Opencode
+
+ with Opencode(base_url="http://127.0.0.1:4096", password="...") as client:
+ for agent in client.agent.list().data:
+ print(agent.id)
+ """
+
+ directory: str | None
+
+ def __init__(
+ self,
+ *,
+ directory: str | os.PathLike[str] | None = None,
+ transport: httpx.BaseTransport | None = None,
+ **kwargs: Any,
+ ) -> None:
+ if transport is not None:
+ if kwargs.get("http_client") is not None:
+ raise ValueError("Pass either http_client or transport, not both")
+ kwargs["http_client"] = httpx.Client(transport=transport)
+ super().__init__(**kwargs)
+ self.directory = _directory(directory)
+
+ @override
+ def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
+ return _with_location(super()._prepare_options(options), self.directory)
+
+ @override
+ def _make_status_error(self, err_msg: str, *, body: object, response: httpx.Response) -> APIStatusError:
+ return tagged_status_error(err_msg, body=body, response=response) or super()._make_status_error(
+ err_msg, body=body, response=response
+ )
+
+ @override
+ def copy(self, *, directory: str | os.PathLike[str] | NotGiven | None = NOT_GIVEN, **kwargs: Any) -> Self:
+ """Returns a client with some options changed, sharing this client's connection pool."""
+ chosen = self.directory if isinstance(directory, NotGiven) else _directory(directory)
+ extra = {**kwargs.pop("_extra_kwargs", {}), "directory": chosen}
+ return super().copy(_extra_kwargs=extra, **kwargs)
+
+ with_options = copy
+
+ @classmethod
+ def from_service(
+ cls,
+ *,
+ start: bool = True,
+ file: str | os.PathLike[str] | None = None,
+ version: Version | None = None,
+ command: Sequence[str] | None = None,
+ env: Mapping[str, str] | None = None,
+ on_start: Callable[[EnsureReason, str | None], None] | None = None,
+ **kwargs: Any,
+ ) -> Self:
+ """Connect to the local background service, starting ``opencode serve --service`` if needed.
+
+ Uses :func:`opencode.service.ensure`, or :func:`opencode.service.discover` when ``start`` is false, and
+ sends the service's password with every request. Other arguments go to the constructor.
+
+ Raises:
+ ServiceError: No matching service is running and ``start`` is false, or it could not be started.
+ """
+ if start:
+ endpoint = ensure_service(file, version, command=command, env=env, on_start=on_start)
+ else:
+ found = discover_service(file, version)
+ if found is None:
+ raise ServiceError("No matching OpenCode background service is running")
+ endpoint = found
+ return cls(
+ base_url=endpoint.url,
+ username=DEFAULT_USERNAME if endpoint.auth is None else endpoint.auth.username,
+ password=None if endpoint.auth is None else endpoint.auth.password,
+ **kwargs,
+ )
+
+ @cached_property
+ def events(self) -> EventHub:
+ """The shared event stream: one connection for every subscriber. See :class:`opencode.EventHub`."""
+ return EventHub(self)
+
+ @cached_property
+ @override
+ def rpc(self) -> TypedRpcResource: # pyright: ignore[reportIncompatibleVariableOverride]
+ """Plugin RPC calls: ``client.rpc.call(...)``, or ``client.rpc(Definition)`` for typed access."""
+ return TypedRpcResource(self)
+
+ @cached_property
+ @override
+ def session(self) -> Session: # pyright: ignore[reportIncompatibleVariableOverride]
+ """Sessions: the generated operations and ``run``."""
+ return Session(self)
+
+ @cached_property
+ @override
+ def pty(self) -> Pty: # pyright: ignore[reportIncompatibleVariableOverride]
+ """Terminals: the generated operations and ``attach``."""
+ return Pty(self)
+
+ @cached_property
+ @override
+ def experimental(self) -> Experimental: # pyright: ignore[reportIncompatibleVariableOverride]
+ """Experimental operations; ``persistent_pty`` can ``attach``."""
+ return Experimental(self)
+
+ @classmethod
+ def fake(cls, server: FakeServer | None = None, **kwargs: Any) -> Self:
+ """Returns a client wired to an in-process :class:`opencode.testing.FakeServer`, for tests.
+
+ Pass your own ``server`` to configure replies and inspect requests; other arguments go to the constructor.
+ """
+ from ..testing import FakeServer
+
+ return cls(base_url=FAKE_BASE_URL, transport=server if server is not None else FakeServer(), **kwargs)
+
+ def wait_until_ready(
+ self, *, agents: Iterable[str] = (), mcp_servers: Iterable[str] = (), timeout: float = 60
+ ) -> None:
+ """Blocks until the server answers, the named ``agents`` are listed and the named ``mcp_servers`` are
+ connected.
+
+ Agents load asynchronously after the server starts, so call this before creating sessions on a freshly
+ booted server. Connection errors and 503s while the server boots count as not ready yet.
+
+ Raises:
+ TimeoutError: Something was still missing after ``timeout`` seconds. The message names it.
+ """
+ from ._ready import wait_until_ready
+
+ wait_until_ready(self, agents=agents, mcp_servers=mcp_servers, timeout=timeout)
+
+
+class AsyncOpencode(_AsyncOpencode):
+ """Asynchronous client for an OpenCode server. Works with asyncio and trio.
+
+ Takes the same arguments as :class:`Opencode`; ``http_client`` is an ``httpx.AsyncClient`` and
+ ``transport`` an ``httpx.AsyncBaseTransport``.
+ """
+
+ directory: str | None
+
+ def __init__(
+ self,
+ *,
+ directory: str | os.PathLike[str] | None = None,
+ transport: httpx.AsyncBaseTransport | None = None,
+ **kwargs: Any,
+ ) -> None:
+ if transport is not None:
+ if kwargs.get("http_client") is not None:
+ raise ValueError("Pass either http_client or transport, not both")
+ kwargs["http_client"] = httpx.AsyncClient(transport=transport)
+ super().__init__(**kwargs)
+ self.directory = _directory(directory)
+
+ @override
+ async def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions:
+ return _with_location(await super()._prepare_options(options), self.directory)
+
+ @override
+ def _make_status_error(self, err_msg: str, *, body: object, response: httpx.Response) -> APIStatusError:
+ return tagged_status_error(err_msg, body=body, response=response) or super()._make_status_error(
+ err_msg, body=body, response=response
+ )
+
+ @override
+ def copy(self, *, directory: str | os.PathLike[str] | NotGiven | None = NOT_GIVEN, **kwargs: Any) -> Self:
+ """Returns a client with some options changed, sharing this client's connection pool."""
+ chosen = self.directory if isinstance(directory, NotGiven) else _directory(directory)
+ extra = {**kwargs.pop("_extra_kwargs", {}), "directory": chosen}
+ return super().copy(_extra_kwargs=extra, **kwargs)
+
+ with_options = copy
+
+ @classmethod
+ async def from_service(
+ cls,
+ *,
+ start: bool = True,
+ file: str | os.PathLike[str] | None = None,
+ version: Version | None = None,
+ command: Sequence[str] | None = None,
+ env: Mapping[str, str] | None = None,
+ on_start: Callable[[EnsureReason, str | None], None] | None = None,
+ **kwargs: Any,
+ ) -> Self:
+ """Async version of :meth:`Opencode.from_service`. Await it: ``client = await AsyncOpencode.from_service()``."""
+ if start:
+ endpoint = await async_ensure(file, version, command=command, env=env, on_start=on_start)
+ else:
+ found = await async_discover(file, version)
+ if found is None:
+ raise ServiceError("No matching OpenCode background service is running")
+ endpoint = found
+ return cls(
+ base_url=endpoint.url,
+ username=DEFAULT_USERNAME if endpoint.auth is None else endpoint.auth.username,
+ password=None if endpoint.auth is None else endpoint.auth.password,
+ **kwargs,
+ )
+
+ @cached_property
+ def events(self) -> AsyncEventHub:
+ """The shared event stream: one connection for every subscriber. See :class:`opencode.AsyncEventHub`."""
+ return AsyncEventHub(self)
+
+ @cached_property
+ @override
+ def rpc(self) -> AsyncTypedRpcResource: # pyright: ignore[reportIncompatibleVariableOverride]
+ """Plugin RPC calls: ``client.rpc.call(...)``, or ``client.rpc(Definition)`` for typed access."""
+ return AsyncTypedRpcResource(self)
+
+ @cached_property
+ @override
+ def session(self) -> AsyncSession: # pyright: ignore[reportIncompatibleVariableOverride]
+ """Sessions: the generated operations and ``run``."""
+ return AsyncSession(self)
+
+ @cached_property
+ @override
+ def pty(self) -> AsyncPty: # pyright: ignore[reportIncompatibleVariableOverride]
+ """Terminals: the generated operations and ``attach``."""
+ return AsyncPty(self)
+
+ @cached_property
+ @override
+ def experimental(self) -> AsyncExperimental: # pyright: ignore[reportIncompatibleVariableOverride]
+ """Experimental operations; ``persistent_pty`` can ``attach``."""
+ return AsyncExperimental(self)
+
+ @classmethod
+ def fake(cls, server: FakeServer | None = None, **kwargs: Any) -> Self:
+ """Returns a client wired to an in-process :class:`opencode.testing.FakeServer`, for tests."""
+ from ..testing import FakeServer
+
+ return cls(base_url=FAKE_BASE_URL, transport=server if server is not None else FakeServer(), **kwargs)
+
+ async def wait_until_ready(
+ self, *, agents: Iterable[str] = (), mcp_servers: Iterable[str] = (), timeout: float = 60
+ ) -> None:
+ """Async version of :meth:`Opencode.wait_until_ready`."""
+ from ._ready import async_wait_until_ready
+
+ await async_wait_until_ready(self, agents=agents, mcp_servers=mcp_servers, timeout=timeout)
diff --git a/src/opencode/lib/_ready.py b/src/opencode/lib/_ready.py
index 0ff35bf..7ddd9cb 100644
--- a/src/opencode/lib/_ready.py
+++ b/src/opencode/lib/_ready.py
@@ -17,7 +17,7 @@
if TYPE_CHECKING:
from collections.abc import Iterable
- from .._client import AsyncOpencode, Opencode
+ from ._client import AsyncOpencode, Opencode
from ..types import AgentListResponse, McpListResponse
__all__ = ["POLL_INTERVAL", "async_wait_until_ready", "wait_until_ready"]
diff --git a/src/opencode/lib/_resources.py b/src/opencode/lib/_resources.py
new file mode 100644
index 0000000..ffd6874
--- /dev/null
+++ b/src/opencode/lib/_resources.py
@@ -0,0 +1,67 @@
+"""Generated resources with the SDK's hand-written methods mixed in.
+
+Custom code, never regenerated: ``client.session`` gains ``run`` (and the other helpers of
+:mod:`opencode.lib._session`), ``client.pty`` and ``client.experimental.persistent_pty`` gain
+``attach`` (:mod:`opencode._pty`). :class:`opencode.Opencode` returns these classes.
+"""
+
+from __future__ import annotations
+
+from .._compat import cached_property
+from .._pty import AsyncPersistentPtyAttachMixin, AsyncPtyAttachMixin, PersistentPtyAttachMixin, PtyAttachMixin
+from ..resources.experimental.experimental import AsyncExperimentalResource, ExperimentalResource
+from ..resources.experimental.persistent_pty import AsyncPersistentPtyResource, PersistentPtyResource
+from ..resources.pty.pty import AsyncPtyResource, PtyResource
+from ..resources.session.session import AsyncSessionResource, SessionResource
+from ._session import AsyncSessionHelpers, SessionHelpers
+
+__all__ = [
+ "AsyncExperimental",
+ "AsyncPersistentPty",
+ "AsyncPty",
+ "AsyncSession",
+ "Experimental",
+ "PersistentPty",
+ "Pty",
+ "Session",
+]
+
+
+class Session(SessionHelpers, SessionResource):
+ """``client.session``: the generated operations, ``run`` and the other session helpers."""
+
+
+class AsyncSession(AsyncSessionHelpers, AsyncSessionResource):
+ """``client.session`` of the async client."""
+
+
+class Pty(PtyAttachMixin, PtyResource):
+ """``client.pty``: the generated operations and ``attach``."""
+
+
+class AsyncPty(AsyncPtyAttachMixin, AsyncPtyResource):
+ """``client.pty`` of the async client."""
+
+
+class PersistentPty(PersistentPtyAttachMixin, PersistentPtyResource):
+ """``client.experimental.persistent_pty``: the generated operations and ``attach``."""
+
+
+class AsyncPersistentPty(AsyncPersistentPtyAttachMixin, AsyncPersistentPtyResource):
+ """``client.experimental.persistent_pty`` of the async client."""
+
+
+class Experimental(ExperimentalResource):
+ """``client.experimental``, whose ``persistent_pty`` can ``attach``."""
+
+ @cached_property
+ def persistent_pty(self) -> PersistentPty: # pyright: ignore[reportIncompatibleVariableOverride]
+ return PersistentPty(self._client)
+
+
+class AsyncExperimental(AsyncExperimentalResource):
+ """``client.experimental`` of the async client."""
+
+ @cached_property
+ def persistent_pty(self) -> AsyncPersistentPty: # pyright: ignore[reportIncompatibleVariableOverride]
+ return AsyncPersistentPty(self._client)
diff --git a/src/opencode/lib/_support.py b/src/opencode/lib/_support.py
index 819e3e5..ba3f569 100644
--- a/src/opencode/lib/_support.py
+++ b/src/opencode/lib/_support.py
@@ -12,7 +12,7 @@
import enum
import functools
from collections.abc import Mapping, Sequence
-from typing import Any, Union, cast
+from typing import Any, Dict, Union, cast
from urllib.parse import quote
import httpx
@@ -22,6 +22,7 @@
__all__ = [
"DEFAULT_USERNAME",
+ "Location",
"Timeout",
"encode_path",
"encode_path_segment",
@@ -34,6 +35,9 @@
DEFAULT_USERNAME = "opencode"
"""The Basic auth user name the OpenCode server expects."""
+Location = Dict[str, object]
+"""Where a request runs on the server, for example ``Location(directory="/repo")``."""
+
Timeout = Union[float, httpx.Timeout, None]
"""A timeout in seconds, an ``httpx.Timeout``, or ``None`` for none."""
diff --git a/src/opencode/testing.py b/src/opencode/testing.py
index ac96261..5085cba 100644
--- a/src/opencode/testing.py
+++ b/src/opencode/testing.py
@@ -28,7 +28,7 @@
import time
from collections.abc import Callable, Iterable, Mapping
from dataclasses import dataclass
-from typing import Any
+from typing import Any, TypeVar
import httpx
@@ -56,7 +56,15 @@
__all__ = ["DEFAULT_MODEL", "FakeServer"]
-DEFAULT_MODEL = ModelRef(id="fake-model", provider_id="fake")
+_M = TypeVar("_M", bound=BaseModel)
+
+
+def _model(cls: type[_M], data: object) -> _M:
+ """Validates ``data`` written with field names or wire names (``provider_id`` or ``providerID``)."""
+ return cls.model_validate(data, by_alias=True, by_name=True)
+
+
+DEFAULT_MODEL = _model(ModelRef, {"id": "fake-model", "provider_id": "fake"})
"""Model reported for sessions created without one."""
Reply = str | Callable[[str], str]
@@ -212,7 +220,7 @@ def _run(self, session: SessionInfo, text: str) -> None:
input_tokens, output_tokens = self.tokens
usage = {"input": input_tokens, "output": output_tokens, "reasoning": 0, "cache": {"read": 0, "write": 0}}
self.messages[session.id] += [
- SessionMessageAssistant.model_validate(
+ _model(SessionMessageAssistant,
{
"id": self._id("msg"),
"type": "assistant",
@@ -225,12 +233,12 @@ def _run(self, session: SessionInfo, text: str) -> None:
"tokens": usage,
}
),
- SessionMessageIdle.model_validate(
+ _model(SessionMessageIdle,
{"id": self._id("msg"), "type": "idle", "time": {"created": now}, "outcome": "succeeded"}
),
]
session.cost += self.cost
- session.tokens = TokenUsageInfo.model_validate(
+ session.tokens = _model(TokenUsageInfo,
{
"input": session.tokens.input + input_tokens,
"output": session.tokens.output + output_tokens,
@@ -241,7 +249,7 @@ def _run(self, session: SessionInfo, text: str) -> None:
session.outcome = "succeeded"
session.time.updated = session.time.idle = now
self._record(
- SessionIdleEvent.model_validate(
+ _model(SessionIdleEvent,
{"id": self._id("evt"), "created": now, "type": "session.idle", "data": {"sessionID": session.id}}
)
)
@@ -249,7 +257,7 @@ def _run(self, session: SessionInfo, text: str) -> None:
# -- routes ---------------------------------------------------------------------------------------------
def _server_info(self, request: httpx.Request, match: re.Match[str]) -> httpx.Response:
- info = ServerInfo.model_validate(
+ info = _model(ServerInfo,
{
"version": "fake",
"pid": os.getpid(),
@@ -263,7 +271,7 @@ def _agent_list(self, request: httpx.Request, match: re.Match[str]) -> httpx.Res
self._polls["agent"] += 1
visible = [agent for agent, after in self._agents if self._polls["agent"] > after]
agents = [
- AgentInfo.model_validate(
+ _model(AgentInfo,
{
"id": agent,
"name": agent,
@@ -280,7 +288,7 @@ def _agent_list(self, request: httpx.Request, match: re.Match[str]) -> httpx.Res
def _mcp_list(self, request: httpx.Request, match: re.Match[str]) -> httpx.Response:
self._polls["mcp"] += 1
servers = [
- McpServer.model_validate(
+ _model(McpServer,
{"name": name, "status": _mcp_status(status if self._polls["mcp"] > after else "pending")}
)
for name, (status, after) in self._mcp.items()
@@ -293,7 +301,7 @@ def _session_create(self, request: httpx.Request, match: re.Match[str]) -> httpx
session_id = body.get("id") or self._id("ses")
if session_id in self.sessions:
raise _ErrorResponse(409, {"_tag": "ConflictError", "message": f"Session already exists: {session_id}"})
- session = SessionInfo.model_validate(
+ session = _model(SessionInfo,
{
"id": session_id,
"parentID": body.get("parentID"),
@@ -312,7 +320,7 @@ def _session_create(self, request: httpx.Request, match: re.Match[str]) -> httpx
self.messages[session.id] = []
self.inbox[session.id] = []
self._record(
- SessionCreatedEvent.model_validate(
+ _model(SessionCreatedEvent,
{
"id": self._id("evt"),
"created": now,
@@ -356,7 +364,7 @@ def _session_agent(self, request: httpx.Request, match: re.Match[str]) -> httpx.
def _session_model(self, request: httpx.Request, match: re.Match[str]) -> httpx.Response:
session = self._session(match["id"])
- session.model = ModelRef.model_validate(self._body(request).get("model"))
+ session.model = _model(ModelRef, self._body(request).get("model"))
return httpx.Response(204)
def _session_prompt(self, request: httpx.Request, match: re.Match[str]) -> httpx.Response:
@@ -368,7 +376,7 @@ def _session_prompt(self, request: httpx.Request, match: re.Match[str]) -> httpx
now = self._now()
message_id = body.get("id") or self._id("msg")
payload = {key: body[key] for key in ("text", "files", "agents", "skills", "metadata") if key in body}
- queued = SessionInboxUser.model_validate(
+ queued = _model(SessionInboxUser,
{
"id": message_id,
"sessionID": session.id,
@@ -382,7 +390,7 @@ def _session_prompt(self, request: httpx.Request, match: re.Match[str]) -> httpx
self.inbox[session.id].append(queued)
else:
self.messages[session.id].append(
- SessionMessageUser.model_validate(
+ _model(SessionMessageUser,
{
"id": message_id,
"type": "user",
@@ -412,7 +420,7 @@ def _message_list(self, request: httpx.Request, match: re.Match[str]) -> httpx.R
return _page(request, items, newest_first_default=False)
def _event_stream(self, request: httpx.Request, match: re.Match[str]) -> httpx.Response:
- connected = ServerConnectedEvent.model_validate({"id": self._id("evt"), "type": "server.connected", "data": {}})
+ connected = _model(ServerConnectedEvent, {"id": self._id("evt"), "type": "server.connected", "data": {}})
chunks = [f"data: {json.dumps(event.to_dict())}\n\n" for event in [connected, *self.events]]
return httpx.Response(200, headers={"content-type": "text/event-stream"}, content="".join(chunks).encode())
diff --git a/tests/custom/conftest.py b/tests/custom/conftest.py
index ee9edc9..7150a96 100644
--- a/tests/custom/conftest.py
+++ b/tests/custom/conftest.py
@@ -24,13 +24,13 @@ def mock_api() -> Iterator[respx.MockRouter]:
@pytest.fixture
def client(mock_api: respx.MockRouter) -> Iterator[Opencode]:
- with Opencode(BASE_URL, password=PASSWORD, max_retries=2) as instance:
+ with Opencode(base_url=BASE_URL, password=PASSWORD, max_retries=2) as instance:
yield instance
@pytest.fixture
async def async_client(mock_api: respx.MockRouter) -> AsyncIterator[AsyncOpencode]:
- async with AsyncOpencode(BASE_URL, password=PASSWORD, max_retries=2) as instance:
+ async with AsyncOpencode(base_url=BASE_URL, password=PASSWORD, max_retries=2) as instance:
yield instance
@@ -43,3 +43,4 @@ async def instant(_: float) -> None:
monkeypatch.setattr("opencode._base_client.time.sleep", lambda _: None)
monkeypatch.setattr("opencode._base_client.anyio.sleep", instant)
+ monkeypatch.setattr("opencode._base_client.asyncio.sleep", instant)
diff --git a/tests/custom/test_events.py b/tests/custom/test_events.py
index 75d112a..2460354 100644
--- a/tests/custom/test_events.py
+++ b/tests/custom/test_events.py
@@ -23,13 +23,18 @@
UnauthorizedError,
session_id_of,
)
-from opencode.types import Event, ServerConnectedEvent, SessionIdleEvent, UnknownEvent
+from opencode.types import Event, ServerConnectedEvent, SessionIdleEvent
from .conftest import BASE_URL, PASSWORD
SSE = {"content-type": "text/event-stream"}
+def is_unknown(event: object) -> bool:
+ """An event type this client has no class for still arrives, with its ``type``."""
+ return getattr(event, "type", None) == "brand.new"
+
+
def frame(payload: dict[str, object]) -> bytes:
return f"data: {json.dumps(payload)}\n\n".encode()
@@ -138,7 +143,7 @@ def test_late_subscriber_gets_server_connected_replayed(client: Opencode, mock_a
with client.events.subscribe() as first:
threading.Thread(target=lambda: server.wait_open(1).push(connected(), other())).start()
assert isinstance(first.get(5), ServerConnectedEvent)
- assert isinstance(first.get(5), UnknownEvent)
+ assert is_unknown(first.get(5))
with client.events.subscribe() as late:
assert isinstance(late.get(5), ServerConnectedEvent)
server.wait_open(1).push(idle("ses_1"))
@@ -163,7 +168,7 @@ def test_slow_subscriber_overflows_alone(
with pytest.raises(EventOverflowError, match="3-event capacity"):
slow.get(5)
feed.push(other())
- assert isinstance(fast.get(5), UnknownEvent)
+ assert is_unknown(fast.get(5))
feed.push(END)
diff --git a/tests/custom/test_rpc.py b/tests/custom/test_rpc.py
index 59fe9fc..cac6560 100644
--- a/tests/custom/test_rpc.py
+++ b/tests/custom/test_rpc.py
@@ -19,7 +19,7 @@
rpc_event,
rpc_method,
)
-from opencode.types import Location
+from opencode.lib import Location
from .conftest import BASE_URL, PASSWORD
From aa5803d225932c93749f1dd68b1b1c4755468c07 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 16:50:31 +0530
Subject: [PATCH 10/41] Port the event, RPC and PTY modules to the generated
client
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
src/opencode/_errors.py | 21 ++++++++++++++++++++-
src/opencode/_pty.py | 14 +++++---------
src/opencode/_rpc.py | 4 ++--
src/opencode/lib/_client.py | 16 +++++++++++-----
src/opencode/lib/_support.py | 8 ++++++++
tests/custom/test_events.py | 8 +++++---
tests/custom/test_helpers.py | 4 ++--
tests/custom/test_live.py | 4 ++--
tests/custom/test_pty.py | 12 ++++++------
tests/custom/test_rpc.py | 2 +-
10 files changed, 62 insertions(+), 31 deletions(-)
diff --git a/src/opencode/_errors.py b/src/opencode/_errors.py
index 40ef782..5261c34 100644
--- a/src/opencode/_errors.py
+++ b/src/opencode/_errors.py
@@ -8,6 +8,7 @@
from __future__ import annotations
+import re
from typing import TYPE_CHECKING, Any, ClassVar, Optional, cast
import pydantic
@@ -69,13 +70,31 @@
]
+def _snake(name: str) -> str:
+ """``sessionID`` -> ``session_id``, as the SDK names fields."""
+ value = re.sub(r"[^0-9A-Za-z]+", "_", name)
+ value = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", value)
+ value = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", value)
+ return re.sub(r"_+", "_", value).strip("_").lower()
+
+
class ErrorBody(BaseModel):
- """The JSON body of an OpenCode error. Fields other than ``_tag`` vary by error and stay as sent."""
+ """The JSON body of an OpenCode error. Fields other than ``_tag`` vary by error; read them by
+ their snake-case name (``error.session_id`` for ``sessionID``), ``None`` when not sent."""
tag: Optional[str] = pydantic.Field(default=None, alias="_tag")
message: Optional[str] = None
+ def __getattr__(self, name: str) -> Any:
+ extra: dict[str, Any] = self.__pydantic_extra__ or {}
+ if name.startswith("_"):
+ return super().__getattr__(name) # type: ignore[misc]
+ for key, value in extra.items():
+ if _snake(key) == name:
+ return value
+ return None # an optional field the server left out, as the typed bodies had it
+
class ContentTooLargeError(APIStatusError):
"""The request body is too large (413)."""
diff --git a/src/opencode/_pty.py b/src/opencode/_pty.py
index 9a1bd17..eb9f667 100644
--- a/src/opencode/_pty.py
+++ b/src/opencode/_pty.py
@@ -36,7 +36,7 @@
from ._exceptions import OpencodeError
from ._resource import AsyncAPIResource, SyncAPIResource
from ._types import NOT_GIVEN, NotGiven
-from .lib._support import encode_path_segment, encode_query
+from .lib._support import default_location, encode_path_segment, encode_query
if TYPE_CHECKING:
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator, Mapping
@@ -455,14 +455,12 @@ def attach(
url = _ws_url(
client.base_url,
f"/api/pty/{encode_path_segment(pty_id)}/connect",
- _pty_query(self._location(location), cursor, token.data.ticket),
+ _pty_query(default_location(location, client.directory), cursor, token.data.ticket),
)
ws = _open_sync(url, _headers(client.username, client.password, extra_headers), open_timeout)
def resize(cols: int, rows: int) -> object:
- from .types import PtyUpdateSize
-
- return client.pty.update(pty_id, location=location, size=PtyUpdateSize(cols=cols, rows=rows))
+ return client.pty.update(pty_id, location=location, size={"cols": cols, "rows": rows})
return PtyConnection(ws, _Protocol(persistent=False, cols=0, rows=0), resize)
@@ -489,14 +487,12 @@ async def attach(
url = _ws_url(
client.base_url,
f"/api/pty/{encode_path_segment(pty_id)}/connect",
- _pty_query(self._location(location), cursor, token.data.ticket),
+ _pty_query(default_location(location, client.directory), cursor, token.data.ticket),
)
ws = await _open_async(url, _headers(client.username, client.password, extra_headers), open_timeout)
async def resize(cols: int, rows: int) -> object:
- from .types import PtyUpdateSize
-
- return await client.pty.update(pty_id, location=location, size=PtyUpdateSize(cols=cols, rows=rows))
+ return await client.pty.update(pty_id, location=location, size={"cols": cols, "rows": rows})
return AsyncPtyConnection(ws, _Protocol(persistent=False, cols=0, rows=0), resize)
diff --git a/src/opencode/_rpc.py b/src/opencode/_rpc.py
index 476b851..3228403 100644
--- a/src/opencode/_rpc.py
+++ b/src/opencode/_rpc.py
@@ -315,8 +315,8 @@ def call(
rpc_id = self.definition.id
try:
result = self._client.rpc.call(
- rpc_id,
method.name,
+ rpc_id=rpc_id,
input=method.encode(input),
location=location,
extra_headers=extra_headers,
@@ -388,8 +388,8 @@ async def call(
rpc_id = self.definition.id
try:
result = await self._client.rpc.call(
- rpc_id,
method.name,
+ rpc_id=rpc_id,
input=method.encode(input),
location=location,
extra_headers=extra_headers,
diff --git a/src/opencode/lib/_client.py b/src/opencode/lib/_client.py
index 0f4df0e..1a9419b 100644
--- a/src/opencode/lib/_client.py
+++ b/src/opencode/lib/_client.py
@@ -53,12 +53,18 @@
def _with_location(options: FinalRequestOptions, directory: str | None) -> FinalRequestOptions:
- """Send ``directory`` as the request's ``location`` unless the call passed one."""
- if directory is None:
- return options
+ """Send ``directory`` as the request's ``location`` unless the call passed one, and leave out
+ ``order`` when a page is fetched by ``cursor``: the server rejects both together (the cursor
+ already encodes the direction), and the next page keeps the first request's parameters."""
params = options.params
- if isinstance(params, dict) and "location" not in params:
- options.params = {**cast("dict[str, object]", params), "location": {"directory": directory}}
+ if not isinstance(params, dict):
+ return options
+ query = cast("dict[str, object]", params)
+ if "cursor" in query and "order" in query:
+ query = {key: value for key, value in query.items() if key != "order"}
+ if directory is not None and "location" not in query:
+ query = {**query, "location": {"directory": directory}}
+ options.params = query
return options
diff --git a/src/opencode/lib/_support.py b/src/opencode/lib/_support.py
index ba3f569..8ee461b 100644
--- a/src/opencode/lib/_support.py
+++ b/src/opencode/lib/_support.py
@@ -23,6 +23,7 @@
__all__ = [
"DEFAULT_USERNAME",
"Location",
+ "default_location",
"Timeout",
"encode_path",
"encode_path_segment",
@@ -42,6 +43,13 @@
"""A timeout in seconds, an ``httpx.Timeout``, or ``None`` for none."""
+def default_location(location: object, directory: str | None) -> object:
+ """``location`` as given, else the client's ``directory`` as the location."""
+ if isinstance(location, (NotGiven, Omit)) and directory is not None:
+ return {"directory": directory}
+ return location
+
+
@functools.cache
def _cached_adapter(tp: Any) -> pydantic.TypeAdapter[Any]:
return pydantic.TypeAdapter(tp)
diff --git a/tests/custom/test_events.py b/tests/custom/test_events.py
index 2460354..f9b88e3 100644
--- a/tests/custom/test_events.py
+++ b/tests/custom/test_events.py
@@ -4,6 +4,7 @@
import collections
import json
+from types import SimpleNamespace
import queue
import threading
from collections.abc import AsyncIterator, Iterator
@@ -278,8 +279,9 @@ def test_without_reconnect_errors_end_every_subscription(client: Opencode, mock_
def test_session_id_of() -> None:
- assert session_id_of(ServerConnectedEvent(id="e", data={})) is None
- assert session_id_of(UnknownEvent.model_validate({"type": "x", "data": {"sessionID": "ses_9"}})) == "ses_9"
+ assert session_id_of(ServerConnectedEvent.construct(id="e", type="server.connected", data={})) is None
+ # An event type this client has no class for: its data stays as sent.
+ assert session_id_of(SimpleNamespace(type="x", data={"sessionID": "ses_9"})) == "ses_9"
def test_generated_subscribe_still_works(client: Opencode, mock_api: respx.MockRouter) -> None:
@@ -307,7 +309,7 @@ async def _wait_feed(server: Server, count: int) -> AsyncFeed:
@pytest.fixture
async def aclient(mock_api: respx.MockRouter) -> AsyncIterator[AsyncOpencode]:
- async with AsyncOpencode(BASE_URL, password=PASSWORD, max_retries=0) as instance:
+ async with AsyncOpencode(base_url=BASE_URL, password=PASSWORD, max_retries=0) as instance:
yield instance
diff --git a/tests/custom/test_helpers.py b/tests/custom/test_helpers.py
index 879d22f..b37bede 100644
--- a/tests/custom/test_helpers.py
+++ b/tests/custom/test_helpers.py
@@ -35,7 +35,7 @@ def _paths(fake: FakeServer) -> list[str]:
def test_run_creates_session_and_returns_result() -> None:
fake = FakeServer(reply=lambda text: f"echo: {text}", cost=0.5, tokens=(100, 20))
client = Opencode.fake(fake)
- model = ModelRef(id="claude", provider_id="anthropic")
+ model = ModelRef.model_validate({"id": "claude", "providerID": "anthropic"})
result = client.session.run("hello", agent="build", model=model)
@@ -249,4 +249,4 @@ def test_fake_errors() -> None:
def test_fake_and_http_client_are_exclusive() -> None:
with pytest.raises(ValueError, match="either"):
- Opencode("http://x", transport=FakeServer(), http_client=httpx.Client())
+ Opencode(base_url="http://x", transport=FakeServer(), http_client=httpx.Client())
diff --git a/tests/custom/test_live.py b/tests/custom/test_live.py
index d181a0b..5ec7084 100644
--- a/tests/custom/test_live.py
+++ b/tests/custom/test_live.py
@@ -86,7 +86,7 @@ def test_event_stream_connects(client: Opencode) -> None:
def test_wrong_password_is_rejected(server: OpencodeServer) -> None:
from opencode import UnauthorizedError
- with Opencode(server.url, password="wrong", max_retries=0) as client, pytest.raises(UnauthorizedError):
+ with Opencode(base_url=server.url, password="wrong", max_retries=0) as client, pytest.raises(UnauthorizedError):
client.server.info()
@@ -163,7 +163,7 @@ def test_wait_until_ready(client: Opencode) -> None:
def test_run_with_unroutable_model(client: Opencode) -> None:
# A model of a provider that does not exist fails before any network call, so no model is called.
# Never prompt without such a model: the default would use OpenCode's hosted free model.
- model = ModelRef(id="none", provider_id="sdk-test-missing")
+ model = ModelRef.model_validate({"id": "none", "providerID": "sdk-test-missing"})
result = client.session.run("hello", model=model, timeout=30)
assert [message.type for message in result.messages] == ["user", "idle"]
assert result.outcome == "failed"
diff --git a/tests/custom/test_pty.py b/tests/custom/test_pty.py
index 21da691..de2fdb5 100644
--- a/tests/custom/test_pty.py
+++ b/tests/custom/test_pty.py
@@ -130,7 +130,7 @@ def test_pty_attach_round_trip(ws_server: str, api: respx.MockRouter, fake: Fake
}
)
with (
- Opencode(ws_server, password=PASSWORD, directory="/repo") as client,
+ Opencode(base_url=ws_server, password=PASSWORD, directory="/repo") as client,
client.pty.attach("pty_1", cursor=3) as term,
):
assert term.recv(timeout=5) == b"replay"
@@ -151,14 +151,14 @@ def test_pty_attach_round_trip(ws_server: str, api: respx.MockRouter, fake: Fake
def test_persistent_closed_before_attached(ws_server: str, api: respx.MockRouter, fake: Fake) -> None:
fake.close_code = 4404
- with Opencode(ws_server, password=PASSWORD) as client, pytest.raises(PtyConnectionError) as caught:
+ with Opencode(base_url=ws_server, password=PASSWORD) as client, pytest.raises(PtyConnectionError) as caught:
client.experimental.persistent_pty.attach("pty_1")
assert caught.value.code == 4404
def test_persistent_pty_attach_frames(ws_server: str, api: respx.MockRouter, fake: Fake) -> None:
events: list[dict[str, Any]] = []
- with Opencode(ws_server, password=PASSWORD) as client:
+ with Opencode(base_url=ws_server, password=PASSWORD) as client:
term = client.experimental.persistent_pty.attach("pty_1", attachment_id="a1", on_event=events.append)
with term:
assert term.recv(timeout=5) == b"replay"
@@ -189,7 +189,7 @@ def test_persistent_pty_attach_frames(ws_server: str, api: respx.MockRouter, fak
def test_not_found_close_raises(ws_server: str, api: respx.MockRouter, fake: Fake) -> None:
fake.close_code = 4404
with (
- Opencode(ws_server, password=PASSWORD) as client,
+ Opencode(base_url=ws_server, password=PASSWORD) as client,
client.pty.attach("pty_1") as term,
pytest.raises(PtyConnectionError) as caught,
):
@@ -200,7 +200,7 @@ def test_not_found_close_raises(ws_server: str, api: respx.MockRouter, fake: Fak
def test_refused_handshake_raises(ws_server: str, api: respx.MockRouter, fake: Fake) -> None:
fake.refuse = True
- with Opencode(ws_server, password=PASSWORD) as client, pytest.raises(PtyConnectionError) as caught:
+ with Opencode(base_url=ws_server, password=PASSWORD) as client, pytest.raises(PtyConnectionError) as caught:
client.pty.attach("pty_1")
assert caught.value.status == 403
@@ -214,7 +214,7 @@ def test_missing_websockets(monkeypatch: pytest.MonkeyPatch, mock_api: respx.Moc
@pytest.mark.anyio
async def test_async_pty_attach(ws_server: str, api: respx.MockRouter, fake: Fake) -> None:
- async with AsyncOpencode(ws_server, password=PASSWORD) as client:
+ async with AsyncOpencode(base_url=ws_server, password=PASSWORD) as client:
async with await client.pty.attach("pty_1") as term:
assert await term.recv() == b"replay"
await term.send("echo hi\n")
diff --git a/tests/custom/test_rpc.py b/tests/custom/test_rpc.py
index cac6560..ba1c335 100644
--- a/tests/custom/test_rpc.py
+++ b/tests/custom/test_rpc.py
@@ -133,7 +133,7 @@ def test_subscribe_filters_and_validates_events(client: Opencode, mock_api: resp
@pytest.fixture
async def aclient(mock_api: respx.MockRouter) -> AsyncIterator[AsyncOpencode]:
- async with AsyncOpencode(BASE_URL, password=PASSWORD, max_retries=0) as instance:
+ async with AsyncOpencode(base_url=BASE_URL, password=PASSWORD, max_retries=0) as instance:
yield instance
From 3246f8cfa09aa023d8c20d704385c50ff11e0c16 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 16:50:36 +0530
Subject: [PATCH 11/41] chore: regenerate SDK
Ferrule-Generated-From: eb88f4d43f54080d26f75d51f3590701575e4082
---
ferrule.lock | 214 ++++++++++++------
src/opencode/resources/agent.py | 2 +-
src/opencode/resources/credential.py | 6 +-
.../resources/experimental/persistent_pty.py | 6 +-
src/opencode/resources/generate.py | 2 +-
src/opencode/resources/integration/command.py | 6 +-
src/opencode/resources/integration/connect.py | 6 +-
.../resources/integration/integration.py | 2 +-
src/opencode/resources/integration/oauth.py | 8 +-
src/opencode/resources/message.py | 2 +-
.../resources/permission/permission.py | 10 +-
src/opencode/resources/permission/saved.py | 4 +-
src/opencode/resources/project.py | 8 +-
src/opencode/resources/provider.py | 2 +-
src/opencode/resources/pty/connect.py | 2 +-
src/opencode/resources/pty/pty.py | 2 +-
src/opencode/resources/session/form.py | 8 +-
src/opencode/resources/session/inbox.py | 6 +-
.../resources/session/instructions/entry.py | 4 +-
src/opencode/resources/session/message.py | 4 +-
src/opencode/resources/session/revert.py | 4 +-
src/opencode/resources/session/session.py | 39 ++--
src/opencode/resources/shell.py | 2 +-
src/opencode/resources/vcs/vcs.py | 2 +-
src/opencode/resources/voice.py | 2 +-
src/opencode/resources/websearch.py | 2 +-
src/opencode/resources/worktree.py | 4 +-
.../types/credential_create_params.py | 6 +-
.../persistent_pty_read_params.py | 2 +-
src/opencode/types/generate_text_params.py | 2 +-
.../integration/command_cancel_params.py | 2 +-
.../integration/command_connect_params.py | 2 +-
.../integration/command_status_params.py | 2 +-
.../integration/connect_external_params.py | 4 +-
.../types/integration/connect_key_params.py | 2 +-
.../types/integration/oauth_cancel_params.py | 2 +-
.../integration/oauth_complete_params.py | 2 +-
.../types/integration/oauth_connect_params.py | 4 +-
.../types/integration/oauth_status_params.py | 2 +-
src/opencode/types/mcp_add_params.py | 4 +-
.../types/permission/saved_list_params.py | 2 +-
.../types/permission_create_params.py | 6 +-
src/opencode/types/permission_reply_params.py | 4 +-
src/opencode/types/project_update_params.py | 6 +-
.../types/session/form_create_params.py | 6 +-
.../types/session/form_reply_params.py | 2 +-
.../types/session/inbox_update_params.py | 4 +-
.../session/instructions/entry_put_params.py | 2 +-
.../types/session/revert_stage_params.py | 2 +-
src/opencode/types/session_command_params.py | 8 +-
src/opencode/types/session_compact_params.py | 4 +-
src/opencode/types/session_create_params.py | 12 +-
src/opencode/types/session_diff_params.py | 2 +-
src/opencode/types/session_fork_params.py | 2 +-
src/opencode/types/session_import__params.py | 4 +-
src/opencode/types/session_list_params.py | 10 +-
src/opencode/types/session_log_params.py | 2 +-
src/opencode/types/session_move_params.py | 4 +-
src/opencode/types/session_prompt_params.py | 10 +-
src/opencode/types/session_shell_params.py | 2 +-
src/opencode/types/session_skill_params.py | 2 +-
src/opencode/types/session_stats_params.py | 2 +-
.../types/session_switch_agent_params.py | 2 +-
.../types/session_switch_model_params.py | 2 +-
.../types/session_synthetic_params.py | 4 +-
src/opencode/types/session_update_params.py | 4 +-
src/opencode/types/shared_params/__init__.py | 73 ++++++
.../types/shared_params/absolute_path.py | 9 +
src/opencode/types/shared_params/agent_id.py | 9 +
.../shared_params/credential_external.py | 23 ++
.../types/shared_params/credential_id.py | 9 +
.../types/shared_params/credential_key.py | 20 ++
.../types/shared_params/credential_o_auth.py | 29 +++
.../types/shared_params/credential_value.py | 14 ++
src/opencode/types/shared_params/event_seq.py | 9 +
.../types/shared_params/file_diff_info.py | 23 ++
.../types/shared_params/form_answer.py | 12 +
.../types/shared_params/form_boolean_field.py | 32 +++
.../shared_params/form_external_field.py | 23 ++
.../types/shared_params/form_field.py | 19 ++
.../types/shared_params/form_fields.py | 12 +
src/opencode/types/shared_params/form_id.py | 9 +
.../types/shared_params/form_integer_field.py | 36 +++
.../types/shared_params/form_metadata.py | 10 +
.../shared_params/form_multiselect_field.py | 43 ++++
.../types/shared_params/form_number_field.py | 36 +++
.../types/shared_params/form_option.py | 19 ++
.../types/shared_params/form_string_field.py | 48 ++++
.../types/shared_params/form_value.py | 12 +
src/opencode/types/shared_params/form_when.py | 20 ++
.../shared_params/instruction_entry_key.py | 9 +
.../shared_params/integration_attempt_id.py | 9 +
.../types/shared_params/integration_id.py | 9 +
.../shared_params/integration_method_id.py | 9 +
.../shared_params/location_public_ref.py | 17 ++
.../types/shared_params/mcp_local_config.py | 52 +++++
.../types/shared_params/mcp_o_auth_config.py | 25 ++
.../types/shared_params/mcp_protocol.py | 9 +
.../types/shared_params/mcp_remote_config.py | 54 +++++
src/opencode/types/shared_params/model_ref.py | 22 ++
src/opencode/types/shared_params/money_usd.py | 9 +
.../types/shared_params/permission_effect.py | 9 +
.../types/shared_params/permission_id.py | 9 +
.../types/shared_params/permission_reply.py | 9 +
.../types/shared_params/permission_rule.py | 21 ++
.../types/shared_params/permission_ruleset.py | 12 +
.../shared_params/permission_saved_id.py | 9 +
.../types/shared_params/permission_source.py | 21 ++
.../persistent_pty_read_lines.py | 9 +
.../types/shared_params/project_commands.py | 16 ++
.../types/shared_params/project_icon.py | 19 ++
.../types/shared_params/project_id.py | 9 +
.../shared_params/prompt_agent_attachment.py | 19 ++
.../prompt_input_file_attachment.py | 23 ++
.../prompt_input_skill_attachment.py | 20 ++
.../types/shared_params/prompt_mention.py | 19 ++
.../types/shared_params/provider_id.py | 9 +
src/opencode/types/shared_params/pty_id.py | 9 +
.../types/shared_params/relative_path.py | 9 +
.../shared_params/session_fork_boundary.py | 34 +++
.../types/shared_params/session_id.py | 9 +
.../shared_params/session_inbox_delivery.py | 9 +
.../types/shared_params/session_info.py | 85 +++++++
.../types/shared_params/session_message_id.py | 9 +
.../types/shared_params/session_metadata.py | 10 +
.../types/shared_params/session_revert.py | 27 +++
.../types/shared_params/sessions_cursor.py | 9 +
src/opencode/types/shared_params/shell_id.py | 9 +
src/opencode/types/shared_params/skill_id.py | 9 +
.../types/shared_params/snapshot_id.py | 9 +
.../types/shared_params/token_usage_info.py | 31 +++
src/opencode/types/shared_params/vcs_mode.py | 9 +
.../shared_params/voice_transport_input.py | 13 ++
.../voice_web_rtc_transport_input.py | 20 ++
.../voice_web_socket_transport_input.py | 15 ++
.../types/shared_params/web_search_id.py | 9 +
src/opencode/types/vc_diff_params.py | 2 +-
src/opencode/types/voice_session_params.py | 2 +-
src/opencode/types/websearch_query_params.py | 2 +-
src/opencode/types/worktree_create_params.py | 4 +-
src/opencode/types/worktree_list_params.py | 2 +-
src/opencode/types/worktree_refresh_params.py | 2 +-
src/opencode/types/worktree_remove_params.py | 4 +-
143 files changed, 1637 insertions(+), 226 deletions(-)
create mode 100644 src/opencode/types/shared_params/__init__.py
create mode 100644 src/opencode/types/shared_params/absolute_path.py
create mode 100644 src/opencode/types/shared_params/agent_id.py
create mode 100644 src/opencode/types/shared_params/credential_external.py
create mode 100644 src/opencode/types/shared_params/credential_id.py
create mode 100644 src/opencode/types/shared_params/credential_key.py
create mode 100644 src/opencode/types/shared_params/credential_o_auth.py
create mode 100644 src/opencode/types/shared_params/credential_value.py
create mode 100644 src/opencode/types/shared_params/event_seq.py
create mode 100644 src/opencode/types/shared_params/file_diff_info.py
create mode 100644 src/opencode/types/shared_params/form_answer.py
create mode 100644 src/opencode/types/shared_params/form_boolean_field.py
create mode 100644 src/opencode/types/shared_params/form_external_field.py
create mode 100644 src/opencode/types/shared_params/form_field.py
create mode 100644 src/opencode/types/shared_params/form_fields.py
create mode 100644 src/opencode/types/shared_params/form_id.py
create mode 100644 src/opencode/types/shared_params/form_integer_field.py
create mode 100644 src/opencode/types/shared_params/form_metadata.py
create mode 100644 src/opencode/types/shared_params/form_multiselect_field.py
create mode 100644 src/opencode/types/shared_params/form_number_field.py
create mode 100644 src/opencode/types/shared_params/form_option.py
create mode 100644 src/opencode/types/shared_params/form_string_field.py
create mode 100644 src/opencode/types/shared_params/form_value.py
create mode 100644 src/opencode/types/shared_params/form_when.py
create mode 100644 src/opencode/types/shared_params/instruction_entry_key.py
create mode 100644 src/opencode/types/shared_params/integration_attempt_id.py
create mode 100644 src/opencode/types/shared_params/integration_id.py
create mode 100644 src/opencode/types/shared_params/integration_method_id.py
create mode 100644 src/opencode/types/shared_params/location_public_ref.py
create mode 100644 src/opencode/types/shared_params/mcp_local_config.py
create mode 100644 src/opencode/types/shared_params/mcp_o_auth_config.py
create mode 100644 src/opencode/types/shared_params/mcp_protocol.py
create mode 100644 src/opencode/types/shared_params/mcp_remote_config.py
create mode 100644 src/opencode/types/shared_params/model_ref.py
create mode 100644 src/opencode/types/shared_params/money_usd.py
create mode 100644 src/opencode/types/shared_params/permission_effect.py
create mode 100644 src/opencode/types/shared_params/permission_id.py
create mode 100644 src/opencode/types/shared_params/permission_reply.py
create mode 100644 src/opencode/types/shared_params/permission_rule.py
create mode 100644 src/opencode/types/shared_params/permission_ruleset.py
create mode 100644 src/opencode/types/shared_params/permission_saved_id.py
create mode 100644 src/opencode/types/shared_params/permission_source.py
create mode 100644 src/opencode/types/shared_params/persistent_pty_read_lines.py
create mode 100644 src/opencode/types/shared_params/project_commands.py
create mode 100644 src/opencode/types/shared_params/project_icon.py
create mode 100644 src/opencode/types/shared_params/project_id.py
create mode 100644 src/opencode/types/shared_params/prompt_agent_attachment.py
create mode 100644 src/opencode/types/shared_params/prompt_input_file_attachment.py
create mode 100644 src/opencode/types/shared_params/prompt_input_skill_attachment.py
create mode 100644 src/opencode/types/shared_params/prompt_mention.py
create mode 100644 src/opencode/types/shared_params/provider_id.py
create mode 100644 src/opencode/types/shared_params/pty_id.py
create mode 100644 src/opencode/types/shared_params/relative_path.py
create mode 100644 src/opencode/types/shared_params/session_fork_boundary.py
create mode 100644 src/opencode/types/shared_params/session_id.py
create mode 100644 src/opencode/types/shared_params/session_inbox_delivery.py
create mode 100644 src/opencode/types/shared_params/session_info.py
create mode 100644 src/opencode/types/shared_params/session_message_id.py
create mode 100644 src/opencode/types/shared_params/session_metadata.py
create mode 100644 src/opencode/types/shared_params/session_revert.py
create mode 100644 src/opencode/types/shared_params/sessions_cursor.py
create mode 100644 src/opencode/types/shared_params/shell_id.py
create mode 100644 src/opencode/types/shared_params/skill_id.py
create mode 100644 src/opencode/types/shared_params/snapshot_id.py
create mode 100644 src/opencode/types/shared_params/token_usage_info.py
create mode 100644 src/opencode/types/shared_params/vcs_mode.py
create mode 100644 src/opencode/types/shared_params/voice_transport_input.py
create mode 100644 src/opencode/types/shared_params/voice_web_rtc_transport_input.py
create mode 100644 src/opencode/types/shared_params/voice_web_socket_transport_input.py
create mode 100644 src/opencode/types/shared_params/web_search_id.py
diff --git a/ferrule.lock b/ferrule.lock
index 53d112a..fbed66f 100644
--- a/ferrule.lock
+++ b/ferrule.lock
@@ -87,65 +87,65 @@
"src/opencode/pagination.py": "938fe8d88193c475cf9b7934b2e50de690f2060754e4458dfdd6657875483339",
"src/opencode/py.typed": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"src/opencode/resources/__init__.py": "d726664313307373400dc208debb419b7c59b91f759629a8a0b880bf4d0a110c",
- "src/opencode/resources/agent.py": "ce008b84dae0db6c167c846b0a9bc2010f27fa2fbc072444e0210c77594d3600",
+ "src/opencode/resources/agent.py": "c4d662521d007543eb7e84ff326ccf3e649959dbcd10ce0b48bd11a0e1094297",
"src/opencode/resources/command.py": "71817de475ecff45179c8be3440307ddb9e76196f4b200057d0ccea1fbed5505",
"src/opencode/resources/config.py": "df61a96eca71052b06735ccd78a9d9c30bdf9663a25fd42b6e3cd21637177390",
- "src/opencode/resources/credential.py": "302d2270736c1835f2a2ffe926cbc990271b54387135a223e1bb0521d8ed7baf",
+ "src/opencode/resources/credential.py": "4e9620f0d4d4016a9fc21373348e14376adf504db5cdfecefa8d041fb1bf7313",
"src/opencode/resources/debug/__init__.py": "4b259cbe61b2fece4ebf71e4287a286f8440020daf88219543a9c7dd3038ec82",
"src/opencode/resources/debug/debug.py": "3257e35af342049cbb2377d97a78136bb35793eb89b1cacfed4e019bf024c2bd",
"src/opencode/resources/debug/location.py": "314e488618b6b3aa5d8528edad62c5bc98a1b085c3fe94a8077847ce4e8a25b1",
"src/opencode/resources/event.py": "1726d900bda94f6a9dda4929787e96972b8d5e11492b9294c5be25e2c11af854",
"src/opencode/resources/experimental/__init__.py": "28feac464d3d9902214d3960dede255c9564614645757eae8faa7df956ab4877",
"src/opencode/resources/experimental/experimental.py": "30e7249ccce9f09e96fe456a89e6f8e603769bb7438f195a984c3824e20960c5",
- "src/opencode/resources/experimental/persistent_pty.py": "f3ac11fe8ab6c560393e7928714c2103219ccab33038f36e4043069d37ebf4ac",
+ "src/opencode/resources/experimental/persistent_pty.py": "524bf688f9117c384e789317b11f382af48801470de5e44a97197336177435e2",
"src/opencode/resources/file.py": "d33d2de768514bec732c430628ba04715be7917ee417abe8da38fd7e22f239af",
"src/opencode/resources/form.py": "f10e411104e93a9ac2c57fd9e3b8c93610a8ccd873e98e5c87345f48c0534664",
- "src/opencode/resources/generate.py": "294617a0360500b3d1b27ab7f64c0864c8d0d6f3ba75f161650c68f54381639c",
+ "src/opencode/resources/generate.py": "d412ea14be79978fafdc7c7f6b9b238e9cf1e2c5757b6c252bf1cdbb6fa4e8c8",
"src/opencode/resources/integration/__init__.py": "0f3f50a21c60d150dfee890354599cec64af5220c7916a2000dd2daab0bfcd6d",
- "src/opencode/resources/integration/command.py": "0509872d6e3026362446d4bd49512c4419e94bdf99f724d65368a678a278f5c7",
- "src/opencode/resources/integration/connect.py": "634655e1240ee0c520289a470b62ba78908543c984af32cdd004ec5adff16887",
- "src/opencode/resources/integration/integration.py": "071a34591d0a50c6c904b2076ee69e0f3e1a638319ea9d110cabf3791cc2915a",
- "src/opencode/resources/integration/oauth.py": "03fc5844ac7e8b7b0e672826f36caa5243442d8f6fd8bd95f78f9f2a383e83b6",
+ "src/opencode/resources/integration/command.py": "90cef6d90aa33ad9c8680ff6cc9baed6d2014feb3416f1a0c99d2bf2f03b21e0",
+ "src/opencode/resources/integration/connect.py": "06963bb5b638d0f96455911d224e31085087324d2da3d016a42689389246b804",
+ "src/opencode/resources/integration/integration.py": "50800493e85ecae80a8d4d72da97e44b69ca48b2b805214a36ab47726e608c2a",
+ "src/opencode/resources/integration/oauth.py": "915dd9bb40bd66ea8ca7c81f8bcddfa3c699adc71f9ad756545a40c7878e94f5",
"src/opencode/resources/integration/wellknown.py": "2adf443327e60c0dee86a25784a3fdcd22780e970468d753ea891e56eb2d2ef5",
"src/opencode/resources/location.py": "26d5e93f24f2e4e6737193572c49a3ed7b91ae75e2464196f0e3f3b94a49005e",
"src/opencode/resources/mcp/__init__.py": "6909d57eb83ae7e40888c5d59b5a1b16385dcb31eb41745bb5ed2a431d5c0959",
"src/opencode/resources/mcp/mcp.py": "ca0a3ce12c799155bf9f8d8468722336a50710241c238746558f8c5ba388f1ba",
"src/opencode/resources/mcp/resource.py": "77be8c8239069effdbdb1e1cdb394cf9d6fb73e0b432390665bd11e9ffed49ef",
- "src/opencode/resources/message.py": "dede2607eaee1315c8397490b3c9e02743b0bf217070824d99b9944089cbb204",
+ "src/opencode/resources/message.py": "6d7c16aa68a3ee3f3357198035ddd699538c92b3432eeaafcd043a668d8bc2b3",
"src/opencode/resources/migration/__init__.py": "4de52a3e69dc12cfa1f3c325d0e451b251117d689b1d2067adf8701d6262a82d",
"src/opencode/resources/migration/migration.py": "2351d44babd9ce815676e40d74acb870cea50fa86664766c561729e899264f56",
"src/opencode/resources/migration/v1.py": "054977ec18a7974e22f870f203652286406e3af93e4131dcd5b04ce8af764026",
"src/opencode/resources/model.py": "2ed756685b0a5967075474e428f82cdea42bb37067e8eb0985ad3afe970be705",
"src/opencode/resources/permission/__init__.py": "fb877b53463f24574016eb09ec406c43bcce5207e0ceb25c2ee6be02f755486b",
- "src/opencode/resources/permission/permission.py": "3cd39a05103aba8db3d8a373a1ca182053a11dea335e2d2ef90c26bd91b58cf9",
+ "src/opencode/resources/permission/permission.py": "fa0dd4c37e21766810e4a43f23257c1c928fe86704359d18894d4bca6b9a24bb",
"src/opencode/resources/permission/request.py": "ddf2ebb3ef0eb186da16e91e65a4f7bdd43058f6595b780755d6e08b41e73f39",
- "src/opencode/resources/permission/saved.py": "5a432d57deea13a08dd589a10fb4c1d51e78d08fba30e2cf3453bc014dcddced",
+ "src/opencode/resources/permission/saved.py": "c4eaf085b352b625f2dfed72bb0ace6529e112cc989d101fb8eea1456eccb916",
"src/opencode/resources/plugin.py": "adddbafb0b0aabc81959183ced02aa4e7975a99a1c3f46e9b9c687044086710c",
- "src/opencode/resources/project.py": "c95e35f9109077334c69c810fb163209802680ab3c50aa700f530f2a7161f38b",
- "src/opencode/resources/provider.py": "070779334044ad73e7292e2d12186e0af5ffc827283312c8a6f1d8e52997e5a3",
+ "src/opencode/resources/project.py": "2ac75b47c4b907943493f7ab24007eb7f9cfd4efa5826ae76ee7dd86af267026",
+ "src/opencode/resources/provider.py": "7f9cf22c6bd6c28949593b35f9f0b57fea7cb7bafc940048bb3a383e042a657c",
"src/opencode/resources/pty/__init__.py": "575979f97e8633490072e1f29781224ee52d1edc824059f168950fe6f1f68a60",
- "src/opencode/resources/pty/connect.py": "a82f53401c234863a2c5b0b5d3a0938cb960a52352118c3f03724df144434c5c",
- "src/opencode/resources/pty/pty.py": "7e4dbd36ff0ac71d35f83bd33c0e4c96677d62377726ad93a7338c3d8501e86d",
+ "src/opencode/resources/pty/connect.py": "c11e3f87ce26dc056b5f1c53ff7f81d8776eff355f5e530d8e832d8fd1b895ce",
+ "src/opencode/resources/pty/pty.py": "54a5658a32700cb1af9124a6692047b9e5ff3ed4d3a33a7e223d08a338e61e81",
"src/opencode/resources/reference.py": "d0285cfa15c46152ce452478df31320ab3fb2fcc7d418099d918c82caa769295",
"src/opencode/resources/rpc.py": "f9dd500e3379befee9978c09bcd6dbad8059189f4f06682094c54dc46b57f886",
"src/opencode/resources/server.py": "cf422b830f84978a4bb7626c8ec130bfb65e8c49670de4c5f52a17547ddb306f",
"src/opencode/resources/session/__init__.py": "acc72ffc26560ac86e9c50583c07264fb140d4168afb9b535368eda32d2283ec",
- "src/opencode/resources/session/form.py": "3afc878622a14e493e0b57bd4365d2ab670f1fa7c6d46db65a0c490396ec0720",
- "src/opencode/resources/session/inbox.py": "203bfbbe962e5219f5cf77e2a0aea78f876ca2ab370a9a78f27b955bfdc624e6",
+ "src/opencode/resources/session/form.py": "ae11719cf251469d2cfabc1ffad78fa0fad7ae27ac73376680fc407b2256024a",
+ "src/opencode/resources/session/inbox.py": "0690130066ea767e0eee2c048d1dfb1e6f576672b1b80099072c42bacbea5555",
"src/opencode/resources/session/instructions/__init__.py": "0f5a6aead7a12d2a4f73046d763f3e65444201724cc069e6793e97a22e34c22d",
- "src/opencode/resources/session/instructions/entry.py": "bdd09fbe179f1d6ffe95d847d831f50232106bb9e358eb36efa842ab29d09cc0",
+ "src/opencode/resources/session/instructions/entry.py": "8989b4410230a2ad7bc50979f1c96b3487a4f674138b386849f9d28f3209d023",
"src/opencode/resources/session/instructions/instructions.py": "af2fca01a9c6ee019b23871a86c48aeb65a8ab10d8fa50ecaa87247fef4918bc",
- "src/opencode/resources/session/message.py": "a61c77f68f3bbd1a866150f76ffb473f77c1f6a1a3ff8ea3f19c091967951069",
- "src/opencode/resources/session/revert.py": "41b29e4f16f446e5dcb72cc3856fe1c4933bb9ca6fb64648715d7cce6f6ffb90",
- "src/opencode/resources/session/session.py": "71b7af4a763d850b13ad02134515049e05454c3f0e38fbe6fefd8628d2f5be9e",
- "src/opencode/resources/shell.py": "cdfaa416d7a57625c1ceeef94498ef74034a9e83b09283e78429ac5e5d07ab10",
+ "src/opencode/resources/session/message.py": "7fa8727aca864415ba8a8b69d09157f2b76f81cf43b59081e4a597388d415ec0",
+ "src/opencode/resources/session/revert.py": "4c82af7a765f5081c18152940fc3ad0ed26f43f0b93ed2ea4e513027599ed0ad",
+ "src/opencode/resources/session/session.py": "f3ff63d37ad95bb2e903339e4f9618aba7e6fade3721cd47fa7ff34212241576",
+ "src/opencode/resources/shell.py": "b3a5624c840588ffcf614c6993860663c9ba0619b4d3825d622d25beee4777a4",
"src/opencode/resources/skill.py": "9bd25b6a394f8b741d7b3d22bfdbdd8222e72569c3ae66be2f15aa03efdd7af2",
"src/opencode/resources/vcs/__init__.py": "abcb3cb291e4a2d846ccda54be7ec502bd72cec9d86826d29034127e828746b3",
"src/opencode/resources/vcs/branch.py": "d5c92150a4fba999411afb728425d5688236a41ad24597116e8b03933540ff18",
- "src/opencode/resources/vcs/vcs.py": "ae8ef54582102103a637bc87b9c711e9e1d15bcc11971b924d7f4d39a418a0c7",
- "src/opencode/resources/voice.py": "e3b703b0d7cd4dd6a83ed1313c50de9bd49f0bff3a2cb8739cabd257c7fe53c3",
- "src/opencode/resources/websearch.py": "b9a6b5fd8cf602193b9e46efc18413b493a540deaf45908482dd2b161fc8148c",
- "src/opencode/resources/worktree.py": "aa9431902c0edd7ae626f9b98aa14775155331cf3c0106e9ff5dfcd8675f0b29",
+ "src/opencode/resources/vcs/vcs.py": "d914a44f08191fb58be17311561e9b4b337da1069fa673aa715d8fd76f40b72a",
+ "src/opencode/resources/voice.py": "f340f12d19f119fcd29ecdaaa23cdbb0fced2e81300e978c9825ac5959259bc3",
+ "src/opencode/resources/websearch.py": "c27a47f32c72e4c3b4319f655f2f954649cfb19eebc1ff7289b3e911a5f81d45",
+ "src/opencode/resources/worktree.py": "f91f3a3d924acf91858207804b487a7f5109689afcdcdb0cf4e938c258acbee3",
"src/opencode/types/__init__.py": "a795fcb0f50eae5d75e8c9e0265761cb9e270b1968ade970464e95e0ca4f9598",
"src/opencode/types/agent_get_params.py": "be8560402450e9c9688692b61277624c480cb073b79844b3255347e256f2786c",
"src/opencode/types/agent_get_response.py": "50ae1d324ae34e602f676251b3a04af01f0232131ddbc4709fb4ec6d604f47eb",
@@ -157,7 +157,7 @@
"src/opencode/types/config_get_response.py": "a2e5e233b18fe161999a509d87910af800d30ce42f986a6ae5916156eca9eafe",
"src/opencode/types/config_shells_response.py": "6077455be2e1e4f8f54232fc96adca92cf0ea2cdc19bc8d155b7111a0ffad103",
"src/opencode/types/config_update_params.py": "39f05b262ed97cea315b70e5597759b4b3802fa7c2a0264e5ba8f425805f2015",
- "src/opencode/types/credential_create_params.py": "81bb399cc9c4598c286ef55ca072000e7948451976c02cc54e6839ee9c8d1dd0",
+ "src/opencode/types/credential_create_params.py": "f8253d846f1cfaab08521ba508ac220c4e2b88fe71eb6472b8b5720d72d421d4",
"src/opencode/types/credential_list_response.py": "25fabbbfbee60a325aa3deb4ecdef5e390256cd5b4e815f364674d78f36f07b9",
"src/opencode/types/credential_update_params.py": "683a39154ddb37c073db77bc9b4217603e1bd65789fb9c085c064634fe7ae8bd",
"src/opencode/types/debug/__init__.py": "73b434d0b2478eb094c964ac2628e4f242b6d6b814fa8c3c4066d356bce791fa",
@@ -167,7 +167,7 @@
"src/opencode/types/experimental/persistent_pty_create_params.py": "236d282e5270c25459a4c10c196817165825d4a566a5f1a4599d53e5ff2c988e",
"src/opencode/types/experimental/persistent_pty_handoff_response.py": "dc578ce4f46ebbc22bd70088289372d200f9ceabe4588879ad57bde682e94e8f",
"src/opencode/types/experimental/persistent_pty_list_response.py": "1e9b6a89f2fd9d1353faf3a425f85ddd53a2ad25ae05502e330e89ebb1c50243",
- "src/opencode/types/experimental/persistent_pty_read_params.py": "6830ea39823b0eab2c0d8b1c80933fdfc144bb37997202bdd6d6b7da4a1f8623",
+ "src/opencode/types/experimental/persistent_pty_read_params.py": "5632a8d7ecedc2c7417e4d8bc0269ee9d007e7f463808fe01c2d0b83373efbe0",
"src/opencode/types/experimental/persistent_pty_read_response.py": "18a9c4b83bbfb14f6db51fa9ae63e384d453019de99c743d02085c0442d38956",
"src/opencode/types/experimental/persistent_pty_update_params.py": "533ed8468baec4147bdf171c8c262f0a0bb66de9324a333ac53bf81701400ee1",
"src/opencode/types/file_find_params.py": "5d84fd583375a9d552ab631395ebd09f1a21bf5f02edc3e7744e592e1eacc800",
@@ -179,21 +179,21 @@
"src/opencode/types/file_write_response.py": "b288457250a6f8b5ba265c34d5e48e2b6c0b05b16d59ae105004b7b2a12abf88",
"src/opencode/types/form_list_params.py": "bdb4b755d1a0f6749574e3440e4c911894d14f2be6522b07c3167711db0910ef",
"src/opencode/types/form_list_response.py": "e4568e238aa6fb2a6d4a737b169f3c7669f59b07605022fe2b07e276a946219b",
- "src/opencode/types/generate_text_params.py": "1759b815153b451de050e10a659d4a35c2fcfa401f45c703e2538306fc26486f",
+ "src/opencode/types/generate_text_params.py": "5522e948c6e699fc31520f0f9bcf705cda8773fb3343dd60034ab104a0b0ccd5",
"src/opencode/types/generate_text_response.py": "51233c77f6297357a6da77026735324d2fce6df5374b8362ec6d75e17f78493e",
"src/opencode/types/integration/__init__.py": "4a86ae7cb6a8f39f127de1ac4093eaada8862d850e60977c64007efba650833a",
- "src/opencode/types/integration/command_cancel_params.py": "8977a985ad00a36b77716679e1fbb7471b70e7057be2841f253551e653c244a0",
- "src/opencode/types/integration/command_connect_params.py": "da1ca04b1c05382b9ac65ed434041917054ccdd58c0913edaebcdfc8ebe4b937",
+ "src/opencode/types/integration/command_cancel_params.py": "0c6b7a66ac3e0052f1a316bbe08f27860426f749f9f7de341c9f9d1e60f75b38",
+ "src/opencode/types/integration/command_connect_params.py": "3bd8ca11cbd7593eda9765b8ade8bafca9e5bfc2437445b87c84471c148c3263",
"src/opencode/types/integration/command_connect_response.py": "c963b142d0e9db07bc8697ed216587c5df9f9800de89a2fc035cd3ce3893c8fb",
- "src/opencode/types/integration/command_status_params.py": "d373985640757bedc6bf4fced49722a73868ccc8647876187c334b50465098aa",
+ "src/opencode/types/integration/command_status_params.py": "fa631d554b4d5b7306019cf8c5cea775986ec83189e0d68b04035c25e638d77a",
"src/opencode/types/integration/command_status_response.py": "4340646c169ad48efac4d28e123c161de0940830553df4805ab92be5c6a18f54",
- "src/opencode/types/integration/connect_external_params.py": "a952200ad85f47fecd99451b034765c16de15a3caa30cdea0e1426591c7de6bb",
- "src/opencode/types/integration/connect_key_params.py": "502f68954fa5463821b9632293c3ccb0f6d3452bf89c3f52c7c8c4c131ad576c",
- "src/opencode/types/integration/oauth_cancel_params.py": "47cae039fd2d5249481b3d182b8d46ae310a397a852761d9fba8f44b25c7aae4",
- "src/opencode/types/integration/oauth_complete_params.py": "54833c60e31757c25b2926f7e433979374240633481fc6ffab2531e842bfe46d",
- "src/opencode/types/integration/oauth_connect_params.py": "f61dccd23ae9d5a152092c60a735d7f98fce7fcd9b251d1be28d22e6b882e137",
+ "src/opencode/types/integration/connect_external_params.py": "fb59ba05108a876cbcc75e43af52aa644ec425fd9290184e6ac4382cb7b67a31",
+ "src/opencode/types/integration/connect_key_params.py": "80e3efe9d7c36a35df5be48e44f05a296ba3404aad9cc60da0f0a56aed8bccbf",
+ "src/opencode/types/integration/oauth_cancel_params.py": "9107297e140c90694e569299319c28776d1757e46cd4a663e2732164b1a8a6f5",
+ "src/opencode/types/integration/oauth_complete_params.py": "d134dcfe0453f4bee66fb67a47e5d6da5d4b0a21896afbc2991d6cc8e02e3dcb",
+ "src/opencode/types/integration/oauth_connect_params.py": "43fee1417aebfc0ec00f5749018acb0c508bdf6dfe77694aebdd84fa86135892",
"src/opencode/types/integration/oauth_connect_response.py": "131972837863cd1ed4930a45915468553d98782f6654652abd95a1b32454e56f",
- "src/opencode/types/integration/oauth_status_params.py": "17e3e0c12cf62d78fdb2b30bba8aeb1bb920569938b93f98acf19c8f2b487a40",
+ "src/opencode/types/integration/oauth_status_params.py": "5083c7e0ebce283c34b53766bbd5bc68cf55378ed460aa6d4cfca711752f0780",
"src/opencode/types/integration/oauth_status_response.py": "b575a8acf45a5746d115f520d21a11c8b4bf523d95303d36a00776bc09a71da1",
"src/opencode/types/integration/wellknown_add_params.py": "cc0bf256f439885c26c25f50eec147b3c423b79457fb025949279eeb1f000f2a",
"src/opencode/types/integration_get_params.py": "df84c8e9e1233f79993ccce1754063f3548db82201b14e5edab04b6271c341b0",
@@ -204,7 +204,7 @@
"src/opencode/types/mcp/__init__.py": "9c7ed27a7b272d4757c074deae0d6d404bf4ff0bd15cc27325a01cf5c0c827c4",
"src/opencode/types/mcp/resource_catalog_params.py": "b064e5553111d38375b5dc76f06aaa69bb67d4759720a02d20c03985f2513a81",
"src/opencode/types/mcp/resource_catalog_response.py": "fd47b711fc25db917d9b5c09395e3d2c59b6e9c455864a1154a346ef8313df69",
- "src/opencode/types/mcp_add_params.py": "1cdcd10ba820824fc1a4967bb48da17765bc1984f94c558b8cbc4bb940a09363",
+ "src/opencode/types/mcp_add_params.py": "c93f312aed9ee3ab9ddbeac865ce6a9fcfecd0ae90b31714b0ad4e72157931a9",
"src/opencode/types/mcp_connect_params.py": "1facefaaff42fc95cadd883c5fbf2de3fe87386297bfe6f1125bc730bfa1eafe",
"src/opencode/types/mcp_disconnect_params.py": "eeb447b80718e4d2366597a37a9877572e4e50286ef0013f6fea96f36514a62c",
"src/opencode/types/mcp_list_params.py": "e0e3ae48719a34bb89b69f9540e05fcb5e78dc85eacb414cc0fe830ec45ca163",
@@ -220,13 +220,13 @@
"src/opencode/types/permission/__init__.py": "b72043e3aea3eabcbb45046369c414813975ab1c9b7d3ec9abc2776d8693fb33",
"src/opencode/types/permission/request_list_params.py": "ffbf3d9b49cace4ea4dc78a751949acd2e04fe41168c934a88442becbf3d54c7",
"src/opencode/types/permission/request_list_response.py": "4acf7bd36662d6763c9bcdd63612cd00f7b8c09741f18555ada14119ddf9eaa5",
- "src/opencode/types/permission/saved_list_params.py": "29ebdf1c7f2e4afc7f23bce5477f2eb0fa7cea84730a1f6d335cf0e15d154a24",
+ "src/opencode/types/permission/saved_list_params.py": "d61c58220262315c9d79a73c71275b894a7ead09d037c5865c7ddec6841fb1ad",
"src/opencode/types/permission/saved_list_response.py": "1264f338e609f6cc4aa85a437a85f8322c0c5f26412f381383a72d539c181485",
"src/opencode/types/permission/saved_update_params.py": "07625ad77994424d91372db2b2407f0704dc1a04882759edaeebfa1811fd2cdb",
- "src/opencode/types/permission_create_params.py": "8cb5a23fae19dae675023a41b34c87cbea43806a6faf54aa32219c15da115e08",
+ "src/opencode/types/permission_create_params.py": "842dc8aaebf37900fea2f70bbc67e43c1088460c20c93020216504c07cb2b068",
"src/opencode/types/permission_create_response.py": "19675e78b835f59e0d426a08bfc204745eec0253bb9089fd9db9f0b9cb1076c0",
"src/opencode/types/permission_list_response.py": "d056cc80f9ac3c9d24000097a03832e84b689c690a0c8911f0c54c9d2560274a",
- "src/opencode/types/permission_reply_params.py": "1c4823e29099ca4bf1a6363d19e283ed120c1bf02a331a5bafb927374d51ba74",
+ "src/opencode/types/permission_reply_params.py": "86199980a7ba5eabbe5b3b292e4e38862347f7e13c975af6a52b2672453cb155",
"src/opencode/types/plugin_check_params.py": "f6aa54d096800e02472aaf07fbf0695fb5304ccf160d332852e7408c748c557f",
"src/opencode/types/plugin_check_response.py": "829b2a88e73530e37ca423a7146549c081f7f4d0d7ee477546d4912d1d452987",
"src/opencode/types/plugin_list_params.py": "21ac9dc0c1b7492b5e338fa89e4e3e6b7be337ad2f5dbf28a01607ffe8bd739e",
@@ -234,7 +234,7 @@
"src/opencode/types/plugin_update_params.py": "8f7374c38c405d27be56cba93e542f1006814947b668b60b399187215f165384",
"src/opencode/types/project_list_params.py": "2940c408742c7bf50978730ef31a5489d256414c5825506907c2d918da039a07",
"src/opencode/types/project_list_response.py": "caafcf7f4943c258b79784762f29b38b97f24865bcfc60b2816f0a8c52558e77",
- "src/opencode/types/project_update_params.py": "32bf1a3ca52f5cffbe173b8a12cbcac720db857c2a2695bdcc94054c9d39a92b",
+ "src/opencode/types/project_update_params.py": "e15e4fb3aacadd3abdf4bd15adebcb98220ae425a9734cf14915436389458d93",
"src/opencode/types/provider_get_params.py": "123ccf9c33d27df470e939ae22bf17bf2fd3b860f1ffb580403cc754e241ab3a",
"src/opencode/types/provider_get_response.py": "218baab99810490c03fa3ff9be2f3139e9cf817a5cc9c2d86195e5600b23b02c",
"src/opencode/types/provider_list_params.py": "afdfa108a723f5a5519dd2d876af419ce96bf57776f2af296c484dc9343c3301",
@@ -256,40 +256,40 @@
"src/opencode/types/rpc_call_params.py": "5e07a35dfb09fa706b5912360c15946fbd40cf3853dbb9af5ba3da13f10766db",
"src/opencode/types/session/__init__.py": "49cbe5f6bd6c807f7bdf671333f53b59f6dc5ac7813f78c4bcd31bc3aeee023b",
"src/opencode/types/session/form_cancel_params.py": "abb421bda795889519748aae780bf1cf2920a906a76a16999a4852e24a1eaa18",
- "src/opencode/types/session/form_create_params.py": "0f0355e363dd54a01442eca9ec10e32688de0f38b36ee7b1eb48224053f29a2b",
+ "src/opencode/types/session/form_create_params.py": "21372ef3411367c42b1b65416e0e5bc6c49b844bf0db0aaa10942be24e3152ce",
"src/opencode/types/session/form_list_response.py": "0af8e2b72afa1c75779f19b0d05b2b21a1ae6720b0c3ddc33e4e43ffd1bf7d01",
- "src/opencode/types/session/form_reply_params.py": "376781129720221bb31d8a7bb37f0d0aebfb8b50ab89bdc6e00dc492f3ab7b6a",
+ "src/opencode/types/session/form_reply_params.py": "f4dec6bf6c97adb01d4f8fd30b9b27b5ea332a29201ccb74d56c52b4f34fdcb1",
"src/opencode/types/session/inbox_list_response.py": "379992565700d57685030eaa17ede454fa009d44537df8faef7869aa8120b8af",
- "src/opencode/types/session/inbox_update_params.py": "650c05c77c5a5f139a6b9435dd9b6fd83c09dc23da45bb3a86f8b4af2a5b7a84",
+ "src/opencode/types/session/inbox_update_params.py": "cdf7d793afe845a53bbd0ef209a4f078dbf260dcf25918882250f1f3ef0bdb78",
"src/opencode/types/session/instructions/__init__.py": "3483aac0cea6dee1c889af1ef0178657ba1b9f3d9f60d7f9cbbb8ca064387df6",
"src/opencode/types/session/instructions/entry_list_response.py": "d196c9110e6b1f9060fdc1788e2c192596d6d7b1731396a7b767575bb2f49a0e",
- "src/opencode/types/session/instructions/entry_put_params.py": "7c5158106d40dad31128b2ed9ab5baf95447ff9b7426ccd71c99c2dee13cbf33",
- "src/opencode/types/session/revert_stage_params.py": "7517f0d130683f228fe7a4bc07350084c4acbec6b00c232b548b61a3b3b93a55",
+ "src/opencode/types/session/instructions/entry_put_params.py": "bae213e39430262fc9a89758924c4bd7ea40f2817b5503970c1c8794ee2a30c8",
+ "src/opencode/types/session/revert_stage_params.py": "ea4187d23eb27454cc927e4b3d83cfce10ed31c5b57dfb16a6d0aa4755836f4d",
"src/opencode/types/session_active_response.py": "389bd4f749871591f572f4ab6447750dbc715dc05397d65d82a01769e90a842a",
- "src/opencode/types/session_command_params.py": "a8d1895135c4fe0f8ff076c1ae8230df9d9557da55d223d697ec56957f70b6da",
- "src/opencode/types/session_compact_params.py": "2b50277e6ffa1b74c9199ff0b892ea3e083e64435e8bafb6bbfef89b2a546a37",
+ "src/opencode/types/session_command_params.py": "d23e61abe93755e8b93afb0d724465c80a091c850b6561efce5d8cb2cdc31884",
+ "src/opencode/types/session_compact_params.py": "fa9d8cd6140cf52c0322a56338f53b6f22855e0bfb2ddb4881158279ea83eeb3",
"src/opencode/types/session_context_response.py": "39d1187b99334011de8470950ed1ccda1490ff196fd55230461e44d923b6292b",
- "src/opencode/types/session_create_params.py": "9525aa09dc85f2954dc25c13b79d30379ff1011455bc9ef19b57fe77f694afa3",
- "src/opencode/types/session_diff_params.py": "f3d6c3f18b35fa4f025d069a809240652f808d973b9e8d169e672619f7e2f121",
+ "src/opencode/types/session_create_params.py": "4ea09421e846155b4c9e974e7eb1474ee9555fb14c16a10e97958d2b3322dd77",
+ "src/opencode/types/session_diff_params.py": "4d5de242aae4a7526569ab3eaacfc71f8684cd4a2f703da98c4091e8448ed4a8",
"src/opencode/types/session_diff_response.py": "651c83a4a53164ce5519815d42f7068d75afb2198664b70a1b90bd9b75a5f9a7",
"src/opencode/types/session_environment_params.py": "a35911520e2c0b82a4c920eef3e2ba65b0bfd3455d5420add40f9e8984668354",
"src/opencode/types/session_export_params.py": "2296b68e545564b41105df5c9a7b891c228106f2a6380bc22ced70be81b65693",
- "src/opencode/types/session_fork_params.py": "ca7ee6b420dcaf50246609a34d407c30e717133f11db7965cd3893c1691494bc",
+ "src/opencode/types/session_fork_params.py": "c5073d8d725e340db844d99ea1e2b441658cb88997a06a4aabdc6018c6311edf",
"src/opencode/types/session_generate_params.py": "2d3d6eaaa87c229c39b8caf327c78bf4d12fcb7da6619357749f97730c622102",
"src/opencode/types/session_generate_response.py": "f88ac3c0ab53644d0d5fbe6b351585e17ea0f097041f87bf67c59ffc2588f955",
- "src/opencode/types/session_import__params.py": "dee2d92a313a4a06635f87020f3dfff97bab4ffed24b4757d5263444265ca3fe",
+ "src/opencode/types/session_import__params.py": "c64017f8bd4e638f558e9b1afc99111bab97ea20679a801fd8e9534231de626c",
"src/opencode/types/session_interrupt_params.py": "8a9349252a0aa18bbf7c4599d63d8d00dd624753eed7f4966365d02181718d94",
- "src/opencode/types/session_list_params.py": "5dab22fbb7c461e3ba721ddc8508159b04384531d872b55384ef72bcdab58069",
- "src/opencode/types/session_log_params.py": "d9442d22629ae55bbb2442d1bae0d7b86a0a5118a314a3d924416e79b96fc482",
- "src/opencode/types/session_move_params.py": "3c6a91501779eb32e860358358e2c3702ac5b56583b85deedeb5cdb9ec029c35",
- "src/opencode/types/session_prompt_params.py": "35bae1071c0ad9db4484af213bea46df5971761c21da8a4b74b48bf786619994",
- "src/opencode/types/session_shell_params.py": "ed4ebb44f2ce91fe821eff9730e25478db8b0036451646a0abfe458e7b11370d",
- "src/opencode/types/session_skill_params.py": "18e26d1594cef3ee145742f85b184de6219148cc551ac7fef65f890a6d42c028",
- "src/opencode/types/session_stats_params.py": "b811496ff06021a0fdb13ca6c3159a1e829b2fb985cf8be9b32a2569309b0fef",
- "src/opencode/types/session_switch_agent_params.py": "8bd095ea38b570191139c65d468abcc2bde78ed565938f0a19d1fc56c6947328",
- "src/opencode/types/session_switch_model_params.py": "c578e2baa2b041057338222b22519affde308abda7f1bb9e556eb534367e59bf",
- "src/opencode/types/session_synthetic_params.py": "a98d79a87e192d25418fed0a13aca1591dccecd548b1c668a9a7077c7cdb191a",
- "src/opencode/types/session_update_params.py": "d764478bc1ea540f95357053339a1f0cf42db8fa7edb903c0a6df0d945ee3719",
+ "src/opencode/types/session_list_params.py": "9261bd7d16f2be1469443a7d95afc55ae141d3bf1213877b317216c94d59decb",
+ "src/opencode/types/session_log_params.py": "ae0b9d766fdf1932571e3b30ff5d59544d99d0606d4d9bd8023b05154913829d",
+ "src/opencode/types/session_move_params.py": "07a606d532ddfd2a6bab7e47227ed20653c287931fb79ce5b80c978b21283714",
+ "src/opencode/types/session_prompt_params.py": "d8f44178853153c0eec6d6ff384addd60c39cea3ff9a1df9c3f865e38a48eee7",
+ "src/opencode/types/session_shell_params.py": "15c62a6dd3dc9add5bea45e28f95d4c946e53544f772557ba1bf6f20d4b55cfc",
+ "src/opencode/types/session_skill_params.py": "c5d6e73b86a2d8bd6cba04d4c3dc53c3fa55dc6f9645876d1b04b5acc253ca3d",
+ "src/opencode/types/session_stats_params.py": "9993f3a1237bfe7003809aebbf5846f47b660e03d68a5c9bc149fdcdfe7ea88f",
+ "src/opencode/types/session_switch_agent_params.py": "a8cdcd13c156d503b90f013c877ffe160835d5e956fa6820c20592affb75bce9",
+ "src/opencode/types/session_switch_model_params.py": "6a84b2031bbad0bb3af3fc8f32d8e2474cc5b034c28871a8366a364316a0c28a",
+ "src/opencode/types/session_synthetic_params.py": "3145b6e9f2c8a3df4ce85aac118c19af1c623b009f85994bf449f4eec3911fcc",
+ "src/opencode/types/session_update_params.py": "b11956698c82c646a45f86f36c8356e7cfcf6db69b71a096ddb80098ff5c99e0",
"src/opencode/types/session_view_params.py": "f8c3de01a8752c1cd41270155ef8ddba34519538469ecb08a493b9838e297862",
"src/opencode/types/shared/__init__.py": "7499a0013c848f831de5ca14529dc97f15a10478934cd0f53c7e50ba95da24a0",
"src/opencode/types/shared/absolute_path.py": "b1218c627f0776ab990413feab99176032e7c46ef042c30eb35dbf43629b82e8",
@@ -652,6 +652,76 @@
"src/opencode/types/shared/worktree_list.py": "69dacb513f404f186315f02683a0128c3faa7d1de10feb9cfa6ed825074b911e",
"src/opencode/types/shared/worktree_resolved_event.py": "0d4ecb3d433f430e18e9af8315326ec847d64bab904ad884d4b50f16c79de638",
"src/opencode/types/shared/worktree_updated_event.py": "c26368288113bdff7ec661c2ea77b104b230ef6ab165d56744722c0fad6a7acb",
+ "src/opencode/types/shared_params/__init__.py": "3494c4c92f394c04ff4a230c492b539946b7ac3df917bcce4740ceba846ed0a8",
+ "src/opencode/types/shared_params/absolute_path.py": "f6c69a859a07e494077d5d15fb0380416d0ca54c833bd55fe768abd2e6a29df2",
+ "src/opencode/types/shared_params/agent_id.py": "f201f85a8a14e6f8687d995331c8647d0543b710f84d394c11f7ed1fa66a7fe1",
+ "src/opencode/types/shared_params/credential_external.py": "a9513fbc3ce87a15ccb9f4b7bc0a4423bdf33b1aa79b968b060f3a95f4fb06b5",
+ "src/opencode/types/shared_params/credential_id.py": "5845b89e56813411fba57e0d63a80754aeb2ea0972136672e88bb583a1447597",
+ "src/opencode/types/shared_params/credential_key.py": "6f56132d16dab32e4bee8db945b89f0ea5b712c1f920d748e487fc17b1a67226",
+ "src/opencode/types/shared_params/credential_o_auth.py": "df44230d8b9668503456dddabb8f7b80bd62010c60c533d3cd8cb7672c5264fe",
+ "src/opencode/types/shared_params/credential_value.py": "407913124d570cafd891c2e846d4a3b1394ba8871b462d5bddfd3bf864c5e823",
+ "src/opencode/types/shared_params/event_seq.py": "c3bb8b69555f9df0a4b7059ef9aa4778966a29d6e20484a21ec557f8b4bc0cba",
+ "src/opencode/types/shared_params/file_diff_info.py": "1317155d5d217f697e37646519dac5edbe115fca07defee56a96edffa37e3917",
+ "src/opencode/types/shared_params/form_answer.py": "1bafb6ae1e209135e5f23de2961dd4411e40d039e37acc381861c1cff2a542a5",
+ "src/opencode/types/shared_params/form_boolean_field.py": "b4b2a3676664f69e1ecca043e3caad75ec1f6d5e377adebc1f11c4ffecce4b3c",
+ "src/opencode/types/shared_params/form_external_field.py": "9fef995d1b7dae1d9924f544be2c0d76db746b12bf75c17c81ad53aa6646844f",
+ "src/opencode/types/shared_params/form_field.py": "c0a93b321aff41374e42777f68cb347199b33f105a0aaf47a24658d52caa5fd0",
+ "src/opencode/types/shared_params/form_fields.py": "789360d094f16d350a2aaadb5660a04619376eeb4f81b7e940cd354e934e09bb",
+ "src/opencode/types/shared_params/form_id.py": "ab5e399a0707c889704c2f02de495e56656217c6cf51fc70b7a9164818d6d1ba",
+ "src/opencode/types/shared_params/form_integer_field.py": "2332386e939346716ea7199af7887796e1a3c1638e11cbd24ac1b14acf78bcf7",
+ "src/opencode/types/shared_params/form_metadata.py": "fb393c0fb17a36c35fc33fde761e3f0c38bb28820b15db95906243d189e4166b",
+ "src/opencode/types/shared_params/form_multiselect_field.py": "ce2ed5fb1efaa4f18838db58bd5825da2a6d9fb4b3c4a5b813f6f0af202fa0ea",
+ "src/opencode/types/shared_params/form_number_field.py": "6390ef2b7b5c6b3823b24bf9b6ca1ac2e7d90cb8981a9a9a4c03555a7e6efd77",
+ "src/opencode/types/shared_params/form_option.py": "b104376a26c0292dd638e09eac0206795181dc2826b40df08668ee96f2a316c0",
+ "src/opencode/types/shared_params/form_string_field.py": "1b1f02976bb02165fd299aaef6b6389d9aa965f3575e21f4d037cc2bc282db0f",
+ "src/opencode/types/shared_params/form_value.py": "861baeb502095e5acc613ec49230592f929c8500a2133f5823d1f53395d76ec5",
+ "src/opencode/types/shared_params/form_when.py": "cd1bcfd68eb4caa365392c1c13e5dbd2bc148fd73c48e9ee6dcc11d526480dca",
+ "src/opencode/types/shared_params/instruction_entry_key.py": "fab129e53416997f610532446e872b5b149091959888dd5e2bf38e7627f54a36",
+ "src/opencode/types/shared_params/integration_attempt_id.py": "63ce226146d02fb539e1155486735793a26c914d0584f349c4290d6e3b2eb2de",
+ "src/opencode/types/shared_params/integration_id.py": "9b08daddfcf4c246ba20d6c8f26fd309314970858cb99f52031db4f670e3e305",
+ "src/opencode/types/shared_params/integration_method_id.py": "10995ad67182a49e47bf7b18135586790620f005ee4598e2881aebd1abc9a6ac",
+ "src/opencode/types/shared_params/location_public_ref.py": "1d1de422102d87ac70edbe2737118fffab8f3add1a515db9a96e1eeac89fa0e1",
+ "src/opencode/types/shared_params/mcp_local_config.py": "f52e66580b62203bb89594c20eae92117e6f5f697866b1410e02b3c4a7f467dd",
+ "src/opencode/types/shared_params/mcp_o_auth_config.py": "459c32036c95fe737e1744c9e38e3519c74700d9e4abafe9b6b5527ca18ed105",
+ "src/opencode/types/shared_params/mcp_protocol.py": "d9dd5588478b51bee724f589073adb4b8628fe0d7407b2987730d6ebbaae190d",
+ "src/opencode/types/shared_params/mcp_remote_config.py": "a4d53d04642b02f3ca9c56979f8b2703dcfdca4a9b98753e1d1f6fec6b80223d",
+ "src/opencode/types/shared_params/model_ref.py": "6318edaf3bb1d9c9cc202a16b5c0080a325fdad170bd19f0e64445ef0960b5bd",
+ "src/opencode/types/shared_params/money_usd.py": "301f54b0127799274572771b4486d020a307a8b5290b477c749ab854988fb0f1",
+ "src/opencode/types/shared_params/permission_effect.py": "973b226f16b742fae7c180e9de15085a1d6713438b86419b23ce77369be5f3f1",
+ "src/opencode/types/shared_params/permission_id.py": "28b69ef185551c43ed2263f1f80028c71e6abc3a1b9af3e3832554116fc04019",
+ "src/opencode/types/shared_params/permission_reply.py": "346bc9cd4a5392497472fdb1070a68ab3e47f43013b873de6204699a25293c66",
+ "src/opencode/types/shared_params/permission_rule.py": "30aa53868e713e9ceeaa4280af27715bdc3aec1ed8491a8ec278c9e23c6ad56d",
+ "src/opencode/types/shared_params/permission_ruleset.py": "5f09c15d6b55911d7deb1b15987f31924545887348bcd04c6812705e5afb50de",
+ "src/opencode/types/shared_params/permission_saved_id.py": "8448eaaec104cd6cebb0b8a9f391dfb9565efac4fdf250e8e33e4b959ed2256b",
+ "src/opencode/types/shared_params/permission_source.py": "8cfab9991ba1f5746649b2a996a8f8411337012970d2fb547afa59a30f5530ee",
+ "src/opencode/types/shared_params/persistent_pty_read_lines.py": "f67b3d4b3d71d19577bc8ae84eca80403a5765e65d0a2974e46c249f2b4979d5",
+ "src/opencode/types/shared_params/project_commands.py": "80ffa724167dd35adb929f167d90eda234a161e6a2c813475f8e017bc613d125",
+ "src/opencode/types/shared_params/project_icon.py": "bcfca2dddfde3438ad1ef6cf682b9102c5738473e682bcbef3446d829b8e11da",
+ "src/opencode/types/shared_params/project_id.py": "39b45226fa8b18fc038c62df14b7269ae5950c317cbf1413a2fdf9f8e4673c98",
+ "src/opencode/types/shared_params/prompt_agent_attachment.py": "2d255ff7d7c17299fcfc08d45db5d0eb129e3646f41e78f3a925c56147343b94",
+ "src/opencode/types/shared_params/prompt_input_file_attachment.py": "0fff8d421842edae58cf911886a2bec7dd2e20485bda368f40f7f72c1d98b105",
+ "src/opencode/types/shared_params/prompt_input_skill_attachment.py": "2ac30f579acd1dc3e5010bd212d5297da30f5720a4301d8db40fc14a6ef69960",
+ "src/opencode/types/shared_params/prompt_mention.py": "f2e1341e5d00377138fca42f85ed1450d7952f2e0342985c810b20d254d5b3c0",
+ "src/opencode/types/shared_params/provider_id.py": "3d38573c4528f3a4dca87b7dd72d90adb9c39271b59f593a287d44b765a1dcb5",
+ "src/opencode/types/shared_params/pty_id.py": "7183fe7b54c6c80641e2202b455d1d69590e07f1a047f314807f062d0468d963",
+ "src/opencode/types/shared_params/relative_path.py": "05142589af51ffcfff4bce898c8988863c69d81847d8ec7ffbedc2fbc4da80e7",
+ "src/opencode/types/shared_params/session_fork_boundary.py": "6fb2e44a287a46ced7b7e734209158cfa3e549303c3a3a89214c42cd42c7dc18",
+ "src/opencode/types/shared_params/session_id.py": "9ec695623466b529be1f920eadc6252e796111836b43c03167ddd82a43a2b8db",
+ "src/opencode/types/shared_params/session_inbox_delivery.py": "629b96822c4fca13e2a3795a7431470d63825198a2b71430a8335f9b5b868883",
+ "src/opencode/types/shared_params/session_info.py": "e433fc12fad9e54f34a20deeb33e7f3fe179c7f942a00608c11951ae5b4268d0",
+ "src/opencode/types/shared_params/session_message_id.py": "e3a397b2d5c751c270d5ed4a2ce063185d07b4e0ebe3fced5d3ee3fed789a49a",
+ "src/opencode/types/shared_params/session_metadata.py": "fa1849069f3267ff4ab463839f10270ff5fd35a15595774ee93ae2ff7e64e1e2",
+ "src/opencode/types/shared_params/session_revert.py": "ffb7e0331242dc21249774495026ea2dc2398fe9ed26c826fb5b03b8f6e03d3d",
+ "src/opencode/types/shared_params/sessions_cursor.py": "e57d5f606c82f63cda1713f1ae798c03e1fac37bdcceabb8846692b8e7f11ae8",
+ "src/opencode/types/shared_params/shell_id.py": "999bdf810fb4165646a506fdc6788c930ba355a40d1abed2c716c0246b5267da",
+ "src/opencode/types/shared_params/skill_id.py": "4003cdbf01e0e97c0ff77fed2fd3f787e7077653046a00e2f05824928900d68a",
+ "src/opencode/types/shared_params/snapshot_id.py": "bcec241180c20de10ef446088a00a779fa5f87613223affabbb7dbc2b48a0a6f",
+ "src/opencode/types/shared_params/token_usage_info.py": "8c64594722d46b1ad6da040accfea9dd27e487f55337014a166b99a4dff4687c",
+ "src/opencode/types/shared_params/vcs_mode.py": "3a2367318221639083141497ece65f5331788b5ae8e995f3b4488a082ef50f9a",
+ "src/opencode/types/shared_params/voice_transport_input.py": "a37f51ab26b1e1b564209e214543c2c4af1ce4c526aa4fcd907375e8e7c8958b",
+ "src/opencode/types/shared_params/voice_web_rtc_transport_input.py": "3407ba7384f284ee89966b60db450de5476aab101537a5b4f05687ab19f1a4a4",
+ "src/opencode/types/shared_params/voice_web_socket_transport_input.py": "102ab0370f320e57a15b06b23fe980bcf409ea3545a861b21356d4c412a9758d",
+ "src/opencode/types/shared_params/web_search_id.py": "42063b3f358a01c73ab2493939e34a4f4c8dca0b04f7628e1cde3369d75437b3",
"src/opencode/types/shell_create_params.py": "aa5e7394da958f8f36bcfe14f62f2e9a08890d65cf9e45eb5f7b112598ce83c8",
"src/opencode/types/shell_create_response.py": "0b6b2c55f151dc476dc7899194ead80a642dba6f5994d6ee9c80cc994e9b51fe",
"src/opencode/types/shell_get_params.py": "ed52da35e0a77297f0def2bf2ee2265776c6456b3bb50dff7f52191e681b972e",
@@ -665,7 +735,7 @@
"src/opencode/types/skill_list_response.py": "3a388a3278c2a583fcf51d435b6d1e980919e33275d75c3725f9f0cf12f3bb00",
"src/opencode/types/vc_base_params.py": "d81a12b8d24d95b8c0bffdff21c452a1c9207807b2bafcc955a562b67a649d2c",
"src/opencode/types/vc_base_response.py": "30791db11a2371944a92cefd07e927809ef7779b796f012d3425742d4f61c060",
- "src/opencode/types/vc_diff_params.py": "56aa3aa944feee0b01fa11e5d66359a0492a80a2a7746731a1f2ca231f6c48c2",
+ "src/opencode/types/vc_diff_params.py": "abc287a2d49d21d9134d5631d02c34bc4de8d22082881c1478f73f8a3179095d",
"src/opencode/types/vc_diff_response.py": "9117d7be750e109d80c7f77ba9503e0aa3eb59f4333ac2c0fa66256ea22a76a3",
"src/opencode/types/vc_get_params.py": "835f68f1395ca48a72b811e8b881c3c67fb327dfc82be9aeaf66d66b51fd5ba7",
"src/opencode/types/vc_get_response.py": "585f8b3a03211a1cf7078a111e0e6cd6a317def6dbedfe275c64e00080c0d437",
@@ -675,16 +745,16 @@
"src/opencode/types/vcs/__init__.py": "6aa742ac46eb50b32e98feb521d12924e5b8629a9500da1bd21eb9751fac654f",
"src/opencode/types/vcs/branch_list_params.py": "d4352cd2db7ce0145e99b8d913cf7fbe533ebf6704f96612b4b50d978b441882",
"src/opencode/types/vcs/branch_list_response.py": "f19ac95438207f47c0ab9d883778a2aa2c932a83f73508e8f1194d7c41cb44a9",
- "src/opencode/types/voice_session_params.py": "483e96b275aa30d59f5b019fa6bd5ec6c7b2c363991e31104aba9c05dbbe96ae",
+ "src/opencode/types/voice_session_params.py": "c1e14ca3bf219b6fa73f1152120c4bb5526453045cd749f48e4238c813c7595d",
"src/opencode/types/voice_session_response.py": "72a592c99668d919780d65722d08efb8cce89976cb9c2178430ff626e088ebeb",
"src/opencode/types/websearch_providers_params.py": "cd876e52d75e5e73ee6d707fc01fbd96362eb82ecbff315f0340823a7d3a993e",
"src/opencode/types/websearch_providers_response.py": "9e51380d76e876169108cd7a2c66ca730fd1490cf0de7b498185c723f7011ee7",
- "src/opencode/types/websearch_query_params.py": "b41bf364dd53a78d1af0eb252601ecaf5e35e4d38623b4403e187b6cee00b987",
+ "src/opencode/types/websearch_query_params.py": "ff4d724aab0110e480fc8a764eaff1997386618c207eaaea4e7ef11e34aea105",
"src/opencode/types/websearch_query_response.py": "19b71107e2fee3f384ae994f761d2211b099b058428388e1c3141951d138b556",
- "src/opencode/types/worktree_create_params.py": "9767cd22a293c6321f4ae6f6d4761b67e00f81c58d422577b69617bdf33f8c2a",
- "src/opencode/types/worktree_list_params.py": "e36467cb00ac1d8559285580b39a35faa1c7babdd21ed1ffc10896a88a43eb02",
- "src/opencode/types/worktree_refresh_params.py": "e01ec8baa91132207c886ef78c7ec46b6b31ae96df12e17838e4b366d2dbdd25",
- "src/opencode/types/worktree_remove_params.py": "08de3ed791fd36f2458f741e7b38dc9792a21659579207b82389a2a28404b93d",
+ "src/opencode/types/worktree_create_params.py": "4eac196ca595353844ab2cb30a0f50190cbfa7eb8fa9377426f560c978414ba8",
+ "src/opencode/types/worktree_list_params.py": "46668b1b6f7c3639a9a3639e4b112ec450d0c4de9c06ce947917b49f8a546918",
+ "src/opencode/types/worktree_refresh_params.py": "8f43a647006977dec54784522c34680f84356e63ce53efe939bd3bcc09cb6db0",
+ "src/opencode/types/worktree_remove_params.py": "c0b667dafc2de46d94819b253dd8c83da709f50a985be7decbcc19552aa76ce3",
"tests/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/debug/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
diff --git a/src/opencode/resources/agent.py b/src/opencode/resources/agent.py
index ebaa0b4..9e1da19 100644
--- a/src/opencode/resources/agent.py
+++ b/src/opencode/resources/agent.py
@@ -18,9 +18,9 @@
async_to_streamed_response_wrapper,
)
from .._base_client import make_request_options
-from ..types.shared.agent_id import AgentID
from ..types.agent_get_response import AgentGetResponse
from ..types.agent_list_response import AgentListResponse
+from ..types.shared_params.agent_id import AgentID
__all__ = ["AgentResource", "AsyncAgentResource"]
diff --git a/src/opencode/resources/credential.py b/src/opencode/resources/credential.py
index 91f635c..48c10dd 100644
--- a/src/opencode/resources/credential.py
+++ b/src/opencode/resources/credential.py
@@ -19,11 +19,11 @@
)
from .._wrappers import DataWrapper
from .._base_client import make_request_options
-from ..types.shared.credential_id import CredentialID
-from ..types.shared.integration_id import IntegrationID
from ..types.shared.credential_entry import CredentialEntry
-from ..types.shared.credential_value import CredentialValue
from ..types.credential_list_response import CredentialListResponse
+from ..types.shared_params.credential_id import CredentialID
+from ..types.shared_params.integration_id import IntegrationID
+from ..types.shared_params.credential_value import CredentialValue
__all__ = ["CredentialResource", "AsyncCredentialResource"]
diff --git a/src/opencode/resources/experimental/persistent_pty.py b/src/opencode/resources/experimental/persistent_pty.py
index fc2304f..ac5a62b 100644
--- a/src/opencode/resources/experimental/persistent_pty.py
+++ b/src/opencode/resources/experimental/persistent_pty.py
@@ -19,12 +19,12 @@
from ..._wrappers import DataWrapper
from ..._base_client import make_request_options
from ...types.experimental import persistent_pty_read_params, persistent_pty_create_params, persistent_pty_update_params
-from ...types.shared.pty_id import PtyID
-from ...types.shared.session_id import SessionID
+from ...types.shared_params.pty_id import PtyID
+from ...types.shared_params.session_id import SessionID
from ...types.shared.persistent_pty_info import PersistentPtyInfo
from ...types.shared.persistent_pty_snapshot import PersistentPtySnapshot
from ...types.shared.pty_ticket_connect_token import PtyTicketConnectToken
-from ...types.shared.persistent_pty_read_lines import PersistentPtyReadLines
+from ...types.shared_params.persistent_pty_read_lines import PersistentPtyReadLines
from ...types.experimental.persistent_pty_list_response import PersistentPtyListResponse
from ...types.experimental.persistent_pty_read_response import PersistentPtyReadResponse
from ...types.experimental.persistent_pty_handoff_response import PersistentPtyHandoffResponse
diff --git a/src/opencode/resources/generate.py b/src/opencode/resources/generate.py
index 642032f..b52ad14 100644
--- a/src/opencode/resources/generate.py
+++ b/src/opencode/resources/generate.py
@@ -19,8 +19,8 @@
)
from .._wrappers import DataWrapper
from .._base_client import make_request_options
-from ..types.shared.model_ref import ModelRef
from ..types.generate_text_response import GenerateTextResponse
+from ..types.shared_params.model_ref import ModelRef
__all__ = ["GenerateResource", "AsyncGenerateResource"]
diff --git a/src/opencode/resources/integration/command.py b/src/opencode/resources/integration/command.py
index b184ffa..85c8123 100644
--- a/src/opencode/resources/integration/command.py
+++ b/src/opencode/resources/integration/command.py
@@ -18,11 +18,11 @@
)
from ..._base_client import make_request_options
from ...types.integration import command_cancel_params, command_status_params, command_connect_params
-from ...types.shared.integration_id import IntegrationID
-from ...types.shared.integration_method_id import IntegrationMethodID
-from ...types.shared.integration_attempt_id import IntegrationAttemptID
+from ...types.shared_params.integration_id import IntegrationID
from ...types.integration.command_status_response import CommandStatusResponse
+from ...types.shared_params.integration_method_id import IntegrationMethodID
from ...types.integration.command_connect_response import CommandConnectResponse
+from ...types.shared_params.integration_attempt_id import IntegrationAttemptID
__all__ = ["CommandResource", "AsyncCommandResource"]
diff --git a/src/opencode/resources/integration/connect.py b/src/opencode/resources/integration/connect.py
index 0bcff9e..33f9a16 100644
--- a/src/opencode/resources/integration/connect.py
+++ b/src/opencode/resources/integration/connect.py
@@ -18,9 +18,9 @@
)
from ..._base_client import make_request_options
from ...types.integration import connect_key_params, connect_external_params
-from ...types.shared.form_answer import FormAnswer
-from ...types.shared.integration_id import IntegrationID
-from ...types.shared.integration_method_id import IntegrationMethodID
+from ...types.shared_params.form_answer import FormAnswer
+from ...types.shared_params.integration_id import IntegrationID
+from ...types.shared_params.integration_method_id import IntegrationMethodID
__all__ = ["ConnectResource", "AsyncConnectResource"]
diff --git a/src/opencode/resources/integration/integration.py b/src/opencode/resources/integration/integration.py
index 371be83..6e34322 100644
--- a/src/opencode/resources/integration/integration.py
+++ b/src/opencode/resources/integration/integration.py
@@ -50,9 +50,9 @@
async_to_streamed_response_wrapper,
)
from ..._base_client import make_request_options
-from ...types.shared.integration_id import IntegrationID
from ...types.integration_get_response import IntegrationGetResponse
from ...types.integration_list_response import IntegrationListResponse
+from ...types.shared_params.integration_id import IntegrationID
__all__ = ["IntegrationResource", "AsyncIntegrationResource"]
diff --git a/src/opencode/resources/integration/oauth.py b/src/opencode/resources/integration/oauth.py
index cf7c090..825f729 100644
--- a/src/opencode/resources/integration/oauth.py
+++ b/src/opencode/resources/integration/oauth.py
@@ -18,12 +18,12 @@
)
from ..._base_client import make_request_options
from ...types.integration import oauth_cancel_params, oauth_status_params, oauth_connect_params, oauth_complete_params
-from ...types.shared.form_answer import FormAnswer
-from ...types.shared.integration_id import IntegrationID
-from ...types.shared.integration_method_id import IntegrationMethodID
-from ...types.shared.integration_attempt_id import IntegrationAttemptID
+from ...types.shared_params.form_answer import FormAnswer
+from ...types.shared_params.integration_id import IntegrationID
from ...types.integration.oauth_status_response import OauthStatusResponse
from ...types.integration.oauth_connect_response import OauthConnectResponse
+from ...types.shared_params.integration_method_id import IntegrationMethodID
+from ...types.shared_params.integration_attempt_id import IntegrationAttemptID
__all__ = ["OauthResource", "AsyncOauthResource"]
diff --git a/src/opencode/resources/message.py b/src/opencode/resources/message.py
index 7402b86..fe91b6d 100644
--- a/src/opencode/resources/message.py
+++ b/src/opencode/resources/message.py
@@ -20,7 +20,7 @@
)
from ..pagination import SyncCursorPage, AsyncCursorPage
from .._base_client import AsyncPaginator, make_request_options
-from ..types.shared.session_id import SessionID
+from ..types.shared_params.session_id import SessionID
from ..types.shared.session_message_info import SessionMessageInfo
__all__ = ["MessageResource", "AsyncMessageResource"]
diff --git a/src/opencode/resources/permission/permission.py b/src/opencode/resources/permission/permission.py
index 5d96856..31b2636 100644
--- a/src/opencode/resources/permission/permission.py
+++ b/src/opencode/resources/permission/permission.py
@@ -35,14 +35,14 @@
)
from ..._wrappers import DataWrapper
from ..._base_client import make_request_options
-from ...types.shared.agent_id import AgentID
-from ...types.shared.session_id import SessionID
-from ...types.shared.permission_id import PermissionID
-from ...types.shared.permission_reply import PermissionReply
+from ...types.shared_params.agent_id import AgentID
from ...types.permission_list_response import PermissionListResponse
-from ...types.shared.permission_source import PermissionSource
+from ...types.shared_params.session_id import SessionID
from ...types.shared.permission_request import PermissionRequest
from ...types.permission_create_response import PermissionCreateResponse
+from ...types.shared_params.permission_id import PermissionID
+from ...types.shared_params.permission_reply import PermissionReply
+from ...types.shared_params.permission_source import PermissionSource
__all__ = ["PermissionResource", "AsyncPermissionResource"]
diff --git a/src/opencode/resources/permission/saved.py b/src/opencode/resources/permission/saved.py
index 71e0582..570351c 100644
--- a/src/opencode/resources/permission/saved.py
+++ b/src/opencode/resources/permission/saved.py
@@ -19,10 +19,10 @@
from ..._wrappers import DataWrapper
from ..._base_client import make_request_options
from ...types.permission import saved_list_params, saved_update_params
-from ...types.shared.project_id import ProjectID
-from ...types.shared.permission_saved_id import PermissionSavedID
+from ...types.shared_params.project_id import ProjectID
from ...types.shared.permission_saved_info import PermissionSavedInfo
from ...types.permission.saved_list_response import SavedListResponse
+from ...types.shared_params.permission_saved_id import PermissionSavedID
__all__ = ["SavedResource", "AsyncSavedResource"]
diff --git a/src/opencode/resources/project.py b/src/opencode/resources/project.py
index 60cc06e..06728f8 100644
--- a/src/opencode/resources/project.py
+++ b/src/opencode/resources/project.py
@@ -19,11 +19,11 @@
)
from .._base_client import make_request_options
from ..types.shared.project import Project
-from ..types.shared.project_id import ProjectID
-from ..types.shared.project_icon import ProjectIcon
-from ..types.shared.absolute_path import AbsolutePath
from ..types.project_list_response import ProjectListResponse
-from ..types.shared.project_commands import ProjectCommands
+from ..types.shared_params.project_id import ProjectID
+from ..types.shared_params.project_icon import ProjectIcon
+from ..types.shared_params.absolute_path import AbsolutePath
+from ..types.shared_params.project_commands import ProjectCommands
__all__ = ["ProjectResource", "AsyncProjectResource"]
diff --git a/src/opencode/resources/provider.py b/src/opencode/resources/provider.py
index b910b34..4448dd9 100644
--- a/src/opencode/resources/provider.py
+++ b/src/opencode/resources/provider.py
@@ -18,9 +18,9 @@
async_to_streamed_response_wrapper,
)
from .._base_client import make_request_options
-from ..types.shared.provider_id import ProviderID
from ..types.provider_get_response import ProviderGetResponse
from ..types.provider_list_response import ProviderListResponse
+from ..types.shared_params.provider_id import ProviderID
__all__ = ["ProviderResource", "AsyncProviderResource"]
diff --git a/src/opencode/resources/pty/connect.py b/src/opencode/resources/pty/connect.py
index da06adc..0475c76 100644
--- a/src/opencode/resources/pty/connect.py
+++ b/src/opencode/resources/pty/connect.py
@@ -18,7 +18,7 @@
)
from ...types.pty import connect_token_params
from ..._base_client import make_request_options
-from ...types.shared.pty_id import PtyID
+from ...types.shared_params.pty_id import PtyID
from ...types.pty.connect_token_response import ConnectTokenResponse
__all__ = ["ConnectResource", "AsyncConnectResource"]
diff --git a/src/opencode/resources/pty/pty.py b/src/opencode/resources/pty/pty.py
index 992d40a..0454f06 100644
--- a/src/opencode/resources/pty/pty.py
+++ b/src/opencode/resources/pty/pty.py
@@ -26,11 +26,11 @@
async_to_streamed_response_wrapper,
)
from ..._base_client import make_request_options
-from ...types.shared.pty_id import PtyID
from ...types.pty_get_response import PtyGetResponse
from ...types.pty_list_response import PtyListResponse
from ...types.pty_create_response import PtyCreateResponse
from ...types.pty_update_response import PtyUpdateResponse
+from ...types.shared_params.pty_id import PtyID
__all__ = ["PtyResource", "AsyncPtyResource"]
diff --git a/src/opencode/resources/session/form.py b/src/opencode/resources/session/form.py
index 60f200b..49c9839 100644
--- a/src/opencode/resources/session/form.py
+++ b/src/opencode/resources/session/form.py
@@ -19,13 +19,13 @@
from ..._wrappers import DataWrapper
from ..._base_client import make_request_options
from ...types.session import form_reply_params, form_cancel_params, form_create_params
-from ...types.shared.form_id import FormID
from ...types.shared.form_info import FormInfo
-from ...types.shared.form_answer import FormAnswer
from ...types.shared.form_detail import FormDetail
-from ...types.shared.form_fields import FormFields
-from ...types.shared.form_metadata import FormMetadata
+from ...types.shared_params.form_id import FormID
+from ...types.shared_params.form_answer import FormAnswer
+from ...types.shared_params.form_fields import FormFields
from ...types.session.form_list_response import FormListResponse
+from ...types.shared_params.form_metadata import FormMetadata
__all__ = ["FormResource", "AsyncFormResource"]
diff --git a/src/opencode/resources/session/inbox.py b/src/opencode/resources/session/inbox.py
index d79acd6..8bf6aca 100644
--- a/src/opencode/resources/session/inbox.py
+++ b/src/opencode/resources/session/inbox.py
@@ -19,10 +19,10 @@
from ..._wrappers import DataWrapper
from ..._base_client import make_request_options
from ...types.session import inbox_update_params
-from ...types.shared.session_id import SessionID
-from ...types.shared.session_message_id import SessionMessageID
+from ...types.shared_params.session_id import SessionID
from ...types.session.inbox_list_response import InboxListResponse
-from ...types.shared.session_inbox_delivery import SessionInboxDelivery
+from ...types.shared_params.session_message_id import SessionMessageID
+from ...types.shared_params.session_inbox_delivery import SessionInboxDelivery
__all__ = ["InboxResource", "AsyncInboxResource"]
diff --git a/src/opencode/resources/session/instructions/entry.py b/src/opencode/resources/session/instructions/entry.py
index 28ae932..fa3b273 100644
--- a/src/opencode/resources/session/instructions/entry.py
+++ b/src/opencode/resources/session/instructions/entry.py
@@ -18,9 +18,9 @@
)
from ...._wrappers import DataWrapper
from ...._base_client import make_request_options
-from ....types.shared.session_id import SessionID
from ....types.session.instructions import entry_put_params
-from ....types.shared.instruction_entry_key import InstructionEntryKey
+from ....types.shared_params.session_id import SessionID
+from ....types.shared_params.instruction_entry_key import InstructionEntryKey
from ....types.session.instructions.entry_list_response import EntryListResponse
__all__ = ["EntryResource", "AsyncEntryResource"]
diff --git a/src/opencode/resources/session/message.py b/src/opencode/resources/session/message.py
index ac2496b..5db17c6 100644
--- a/src/opencode/resources/session/message.py
+++ b/src/opencode/resources/session/message.py
@@ -18,9 +18,9 @@
)
from ..._wrappers import DataWrapper
from ..._base_client import make_request_options
-from ...types.shared.session_id import SessionID
-from ...types.shared.session_message_id import SessionMessageID
+from ...types.shared_params.session_id import SessionID
from ...types.shared.session_message_info import SessionMessageInfo
+from ...types.shared_params.session_message_id import SessionMessageID
__all__ = ["MessageResource", "AsyncMessageResource"]
diff --git a/src/opencode/resources/session/revert.py b/src/opencode/resources/session/revert.py
index 0a3444f..d212ce0 100644
--- a/src/opencode/resources/session/revert.py
+++ b/src/opencode/resources/session/revert.py
@@ -19,9 +19,9 @@
from ..._wrappers import DataWrapper
from ..._base_client import make_request_options
from ...types.session import revert_stage_params
-from ...types.shared.session_id import SessionID
from ...types.shared.session_revert import SessionRevert
-from ...types.shared.session_message_id import SessionMessageID
+from ...types.shared_params.session_id import SessionID
+from ...types.shared_params.session_message_id import SessionMessageID
__all__ = ["RevertResource", "AsyncRevertResource"]
diff --git a/src/opencode/resources/session/session.py b/src/opencode/resources/session/session.py
index eeb2ad0..148e20f 100644
--- a/src/opencode/resources/session/session.py
+++ b/src/opencode/resources/session/session.py
@@ -76,12 +76,6 @@
from ..._wrappers import DataWrapper
from ...pagination import SyncCursorPage, AsyncCursorPage
from ..._base_client import AsyncPaginator, make_request_options
-from ...types.shared.agent_id import AgentID
-from ...types.shared.skill_id import SkillID
-from ...types.shared.event_seq import EventSeq
-from ...types.shared.model_ref import ModelRef
-from ...types.shared.project_id import ProjectID
-from ...types.shared.session_id import SessionID
from .instructions.instructions import (
InstructionsResource,
AsyncInstructionsResource,
@@ -91,27 +85,34 @@
AsyncInstructionsResourceWithStreamingResponse,
)
from ...types.shared.session_info import SessionInfo
-from ...types.shared.absolute_path import AbsolutePath
-from ...types.shared.relative_path import RelativePath
from ...types.session_diff_response import SessionDiffResponse
-from ...types.shared.sessions_cursor import SessionsCursor
+from ...types.shared_params.agent_id import AgentID
+from ...types.shared_params.skill_id import SkillID
from ...types.session_active_response import SessionActiveResponse
-from ...types.shared.session_metadata import SessionMetadata
+from ...types.shared_params.event_seq import EventSeq
+from ...types.shared_params.model_ref import ModelRef
from ...types.session_context_response import SessionContextResponse
+from ...types.shared_params.project_id import ProjectID
+from ...types.shared_params.session_id import SessionID
from ...types.session_generate_response import SessionGenerateResponse
-from ...types.shared.permission_ruleset import PermissionRuleset
from ...types.shared.session_inbox_user import SessionInboxUser
-from ...types.shared.session_message_id import SessionMessageID
from ...types.shared.session_stats_info import SessionStatsInfo
-from ...types.shared.location_public_ref import LocationPublicRef
+from ...types.shared_params.session_info import SessionInfo as SharedParamsSessionInfo
+from ...types.shared_params.absolute_path import AbsolutePath
+from ...types.shared_params.relative_path import RelativePath
from ...types.shared.session_transfer_data import SessionTransferData
-from ...types.shared.session_inbox_delivery import SessionInboxDelivery
-from ...types.shared.prompt_agent_attachment import PromptAgentAttachment
+from ...types.shared_params.sessions_cursor import SessionsCursor
from ...types.shared.session_inbox_synthetic import SessionInboxSynthetic
+from ...types.shared_params.session_metadata import SessionMetadata
from ...types.shared.session_inbox_compaction import SessionInboxCompaction
+from ...types.shared_params.permission_ruleset import PermissionRuleset
+from ...types.shared_params.session_message_id import SessionMessageID
from ...types.shared.session_interrupt_response import SessionInterruptResponse
-from ...types.shared.prompt_input_file_attachment import PromptInputFileAttachment
-from ...types.shared.prompt_input_skill_attachment import PromptInputSkillAttachment
+from ...types.shared_params.location_public_ref import LocationPublicRef
+from ...types.shared_params.session_inbox_delivery import SessionInboxDelivery
+from ...types.shared_params.prompt_agent_attachment import PromptAgentAttachment
+from ...types.shared_params.prompt_input_file_attachment import PromptInputFileAttachment
+from ...types.shared_params.prompt_input_skill_attachment import PromptInputSkillAttachment
__all__ = ["SessionResource", "AsyncSessionResource"]
@@ -779,7 +780,7 @@ def get(
def import_(
self,
*,
- info: SessionInfo,
+ info: SharedParamsSessionInfo,
messages: Iterable[object],
location: Optional[LocationPublicRef] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -2049,7 +2050,7 @@ async def get(
async def import_(
self,
*,
- info: SessionInfo,
+ info: SharedParamsSessionInfo,
messages: Iterable[object],
location: Optional[LocationPublicRef] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
diff --git a/src/opencode/resources/shell.py b/src/opencode/resources/shell.py
index ea1b71d..516b7cf 100644
--- a/src/opencode/resources/shell.py
+++ b/src/opencode/resources/shell.py
@@ -18,11 +18,11 @@
async_to_streamed_response_wrapper,
)
from .._base_client import make_request_options
-from ..types.shared.shell_id import ShellID
from ..types.shell_get_response import ShellGetResponse
from ..types.shell_list_response import ShellListResponse
from ..types.shell_create_response import ShellCreateResponse
from ..types.shell_output_response import ShellOutputResponse
+from ..types.shared_params.shell_id import ShellID
__all__ = ["ShellResource", "AsyncShellResource"]
diff --git a/src/opencode/resources/vcs/vcs.py b/src/opencode/resources/vcs/vcs.py
index 3b91fea..ba959f2 100644
--- a/src/opencode/resources/vcs/vcs.py
+++ b/src/opencode/resources/vcs/vcs.py
@@ -26,11 +26,11 @@
async_to_streamed_response_wrapper,
)
from ..._base_client import make_request_options
-from ...types.shared.vcs_mode import VcsMode
from ...types.vc_get_response import VcGetResponse
from ...types.vc_base_response import VcBaseResponse
from ...types.vc_diff_response import VcDiffResponse
from ...types.vc_status_response import VcStatusResponse
+from ...types.shared_params.vcs_mode import VcsMode
__all__ = ["VcsResource", "AsyncVcsResource"]
diff --git a/src/opencode/resources/voice.py b/src/opencode/resources/voice.py
index 1909d7b..b15e3f5 100644
--- a/src/opencode/resources/voice.py
+++ b/src/opencode/resources/voice.py
@@ -20,7 +20,7 @@
from .._wrappers import DataWrapper
from .._base_client import make_request_options
from ..types.voice_session_response import VoiceSessionResponse
-from ..types.shared.voice_transport_input import VoiceTransportInput
+from ..types.shared_params.voice_transport_input import VoiceTransportInput
__all__ = ["VoiceResource", "AsyncVoiceResource"]
diff --git a/src/opencode/resources/websearch.py b/src/opencode/resources/websearch.py
index b45fd98..c91245d 100644
--- a/src/opencode/resources/websearch.py
+++ b/src/opencode/resources/websearch.py
@@ -18,8 +18,8 @@
async_to_streamed_response_wrapper,
)
from .._base_client import make_request_options
-from ..types.shared.web_search_id import WebSearchID
from ..types.websearch_query_response import WebsearchQueryResponse
+from ..types.shared_params.web_search_id import WebSearchID
from ..types.websearch_providers_response import WebsearchProvidersResponse
__all__ = ["WebsearchResource", "AsyncWebsearchResource"]
diff --git a/src/opencode/resources/worktree.py b/src/opencode/resources/worktree.py
index 041d9cf..4814a8e 100644
--- a/src/opencode/resources/worktree.py
+++ b/src/opencode/resources/worktree.py
@@ -16,10 +16,10 @@
async_to_streamed_response_wrapper,
)
from .._base_client import make_request_options
-from ..types.shared.project_id import ProjectID
-from ..types.shared.absolute_path import AbsolutePath
from ..types.shared.worktree_info import WorktreeInfo
from ..types.shared.worktree_list import WorktreeList
+from ..types.shared_params.project_id import ProjectID
+from ..types.shared_params.absolute_path import AbsolutePath
__all__ = ["WorktreeResource", "AsyncWorktreeResource"]
diff --git a/src/opencode/types/credential_create_params.py b/src/opencode/types/credential_create_params.py
index f03f283..d6d2e71 100644
--- a/src/opencode/types/credential_create_params.py
+++ b/src/opencode/types/credential_create_params.py
@@ -5,9 +5,9 @@
from typing_extensions import Required, Annotated, TypedDict
from .._utils import PropertyInfo
-from .shared.credential_id import CredentialID
-from .shared.integration_id import IntegrationID
-from .shared.credential_value import CredentialValue
+from .shared_params.credential_id import CredentialID
+from .shared_params.integration_id import IntegrationID
+from .shared_params.credential_value import CredentialValue
__all__ = ["CredentialCreateParams"]
diff --git a/src/opencode/types/experimental/persistent_pty_read_params.py b/src/opencode/types/experimental/persistent_pty_read_params.py
index ebe2c21..217cabe 100644
--- a/src/opencode/types/experimental/persistent_pty_read_params.py
+++ b/src/opencode/types/experimental/persistent_pty_read_params.py
@@ -5,7 +5,7 @@
from typing import Optional
from typing_extensions import TypedDict
-from ..shared.persistent_pty_read_lines import PersistentPtyReadLines
+from ..shared_params.persistent_pty_read_lines import PersistentPtyReadLines
__all__ = ["PersistentPtyReadParams"]
diff --git a/src/opencode/types/generate_text_params.py b/src/opencode/types/generate_text_params.py
index 436aff3..6227a00 100644
--- a/src/opencode/types/generate_text_params.py
+++ b/src/opencode/types/generate_text_params.py
@@ -5,7 +5,7 @@
from typing import Optional
from typing_extensions import Required, TypedDict
-from .shared.model_ref import ModelRef
+from .shared_params.model_ref import ModelRef
__all__ = ["GenerateTextParams"]
diff --git a/src/opencode/types/integration/command_cancel_params.py b/src/opencode/types/integration/command_cancel_params.py
index f13611b..1faae75 100644
--- a/src/opencode/types/integration/command_cancel_params.py
+++ b/src/opencode/types/integration/command_cancel_params.py
@@ -6,7 +6,7 @@
from typing_extensions import Required, Annotated, TypeAlias, TypedDict
from ..._utils import PropertyInfo
-from ..shared.integration_id import IntegrationID
+from ..shared_params.integration_id import IntegrationID
__all__ = ["CommandCancelParams", "Location"]
diff --git a/src/opencode/types/integration/command_connect_params.py b/src/opencode/types/integration/command_connect_params.py
index 9619daa..8f20cea 100644
--- a/src/opencode/types/integration/command_connect_params.py
+++ b/src/opencode/types/integration/command_connect_params.py
@@ -6,7 +6,7 @@
from typing_extensions import Required, Annotated, TypeAlias, TypedDict
from ..._utils import PropertyInfo
-from ..shared.integration_method_id import IntegrationMethodID
+from ..shared_params.integration_method_id import IntegrationMethodID
__all__ = ["CommandConnectParams", "Location"]
diff --git a/src/opencode/types/integration/command_status_params.py b/src/opencode/types/integration/command_status_params.py
index afe5787..cb32959 100644
--- a/src/opencode/types/integration/command_status_params.py
+++ b/src/opencode/types/integration/command_status_params.py
@@ -6,7 +6,7 @@
from typing_extensions import Required, Annotated, TypeAlias, TypedDict
from ..._utils import PropertyInfo
-from ..shared.integration_id import IntegrationID
+from ..shared_params.integration_id import IntegrationID
__all__ = ["CommandStatusParams", "Location"]
diff --git a/src/opencode/types/integration/connect_external_params.py b/src/opencode/types/integration/connect_external_params.py
index bbbb6da..664b0ca 100644
--- a/src/opencode/types/integration/connect_external_params.py
+++ b/src/opencode/types/integration/connect_external_params.py
@@ -6,8 +6,8 @@
from typing_extensions import Required, Annotated, TypeAlias, TypedDict
from ..._utils import PropertyInfo
-from ..shared.form_answer import FormAnswer
-from ..shared.integration_method_id import IntegrationMethodID
+from ..shared_params.form_answer import FormAnswer
+from ..shared_params.integration_method_id import IntegrationMethodID
__all__ = ["ConnectExternalParams", "Location"]
diff --git a/src/opencode/types/integration/connect_key_params.py b/src/opencode/types/integration/connect_key_params.py
index 598a586..c8ef8a5 100644
--- a/src/opencode/types/integration/connect_key_params.py
+++ b/src/opencode/types/integration/connect_key_params.py
@@ -5,7 +5,7 @@
from typing import Dict, Union, Optional
from typing_extensions import Required, TypeAlias, TypedDict
-from ..shared.form_answer import FormAnswer
+from ..shared_params.form_answer import FormAnswer
__all__ = ["ConnectKeyParams", "Location"]
diff --git a/src/opencode/types/integration/oauth_cancel_params.py b/src/opencode/types/integration/oauth_cancel_params.py
index ea00c0d..f248bf4 100644
--- a/src/opencode/types/integration/oauth_cancel_params.py
+++ b/src/opencode/types/integration/oauth_cancel_params.py
@@ -6,7 +6,7 @@
from typing_extensions import Required, Annotated, TypeAlias, TypedDict
from ..._utils import PropertyInfo
-from ..shared.integration_id import IntegrationID
+from ..shared_params.integration_id import IntegrationID
__all__ = ["OauthCancelParams", "Location"]
diff --git a/src/opencode/types/integration/oauth_complete_params.py b/src/opencode/types/integration/oauth_complete_params.py
index 5a89f1f..8723744 100644
--- a/src/opencode/types/integration/oauth_complete_params.py
+++ b/src/opencode/types/integration/oauth_complete_params.py
@@ -6,7 +6,7 @@
from typing_extensions import Required, Annotated, TypeAlias, TypedDict
from ..._utils import PropertyInfo
-from ..shared.integration_id import IntegrationID
+from ..shared_params.integration_id import IntegrationID
__all__ = ["OauthCompleteParams", "Location"]
diff --git a/src/opencode/types/integration/oauth_connect_params.py b/src/opencode/types/integration/oauth_connect_params.py
index 109c360..25d3b62 100644
--- a/src/opencode/types/integration/oauth_connect_params.py
+++ b/src/opencode/types/integration/oauth_connect_params.py
@@ -6,8 +6,8 @@
from typing_extensions import Required, Annotated, TypeAlias, TypedDict
from ..._utils import PropertyInfo
-from ..shared.form_answer import FormAnswer
-from ..shared.integration_method_id import IntegrationMethodID
+from ..shared_params.form_answer import FormAnswer
+from ..shared_params.integration_method_id import IntegrationMethodID
__all__ = ["OauthConnectParams", "Location"]
diff --git a/src/opencode/types/integration/oauth_status_params.py b/src/opencode/types/integration/oauth_status_params.py
index 3449959..bff7b70 100644
--- a/src/opencode/types/integration/oauth_status_params.py
+++ b/src/opencode/types/integration/oauth_status_params.py
@@ -6,7 +6,7 @@
from typing_extensions import Required, Annotated, TypeAlias, TypedDict
from ..._utils import PropertyInfo
-from ..shared.integration_id import IntegrationID
+from ..shared_params.integration_id import IntegrationID
__all__ = ["OauthStatusParams", "Location"]
diff --git a/src/opencode/types/mcp_add_params.py b/src/opencode/types/mcp_add_params.py
index 33177cd..ca35805 100644
--- a/src/opencode/types/mcp_add_params.py
+++ b/src/opencode/types/mcp_add_params.py
@@ -5,8 +5,8 @@
from typing import Dict, Union, Optional
from typing_extensions import Required, TypeAlias, TypedDict
-from .shared.mcp_local_config import McpLocalConfig
-from .shared.mcp_remote_config import McpRemoteConfig
+from .shared_params.mcp_local_config import McpLocalConfig
+from .shared_params.mcp_remote_config import McpRemoteConfig
__all__ = ["McpAddParams", "Config", "Location"]
diff --git a/src/opencode/types/permission/saved_list_params.py b/src/opencode/types/permission/saved_list_params.py
index c96aebd..240d500 100644
--- a/src/opencode/types/permission/saved_list_params.py
+++ b/src/opencode/types/permission/saved_list_params.py
@@ -6,7 +6,7 @@
from typing_extensions import Annotated, TypedDict
from ..._utils import PropertyInfo
-from ..shared.project_id import ProjectID
+from ..shared_params.project_id import ProjectID
__all__ = ["SavedListParams"]
diff --git a/src/opencode/types/permission_create_params.py b/src/opencode/types/permission_create_params.py
index 6e21db7..17b1d3d 100644
--- a/src/opencode/types/permission_create_params.py
+++ b/src/opencode/types/permission_create_params.py
@@ -6,9 +6,9 @@
from typing_extensions import Required, TypedDict
from .._types import SequenceNotStr
-from .shared.agent_id import AgentID
-from .shared.permission_id import PermissionID
-from .shared.permission_source import PermissionSource
+from .shared_params.agent_id import AgentID
+from .shared_params.permission_id import PermissionID
+from .shared_params.permission_source import PermissionSource
__all__ = ["PermissionCreateParams"]
diff --git a/src/opencode/types/permission_reply_params.py b/src/opencode/types/permission_reply_params.py
index 81b32f7..3785945 100644
--- a/src/opencode/types/permission_reply_params.py
+++ b/src/opencode/types/permission_reply_params.py
@@ -6,8 +6,8 @@
from typing_extensions import Required, Annotated, TypedDict
from .._utils import PropertyInfo
-from .shared.session_id import SessionID
-from .shared.permission_reply import PermissionReply
+from .shared_params.session_id import SessionID
+from .shared_params.permission_reply import PermissionReply
__all__ = ["PermissionReplyParams"]
diff --git a/src/opencode/types/project_update_params.py b/src/opencode/types/project_update_params.py
index 92aa1fd..06c2654 100644
--- a/src/opencode/types/project_update_params.py
+++ b/src/opencode/types/project_update_params.py
@@ -4,9 +4,9 @@
from typing_extensions import TypedDict
-from .shared.project_icon import ProjectIcon
-from .shared.absolute_path import AbsolutePath
-from .shared.project_commands import ProjectCommands
+from .shared_params.project_icon import ProjectIcon
+from .shared_params.absolute_path import AbsolutePath
+from .shared_params.project_commands import ProjectCommands
__all__ = ["ProjectUpdateParams"]
diff --git a/src/opencode/types/session/form_create_params.py b/src/opencode/types/session/form_create_params.py
index 9998a84..8ba485e 100644
--- a/src/opencode/types/session/form_create_params.py
+++ b/src/opencode/types/session/form_create_params.py
@@ -5,9 +5,9 @@
from typing import Optional
from typing_extensions import Required, TypedDict
-from ..shared.form_id import FormID
-from ..shared.form_fields import FormFields
-from ..shared.form_metadata import FormMetadata
+from ..shared_params.form_id import FormID
+from ..shared_params.form_fields import FormFields
+from ..shared_params.form_metadata import FormMetadata
__all__ = ["FormCreateParams"]
diff --git a/src/opencode/types/session/form_reply_params.py b/src/opencode/types/session/form_reply_params.py
index 5ef6eb4..bf571d7 100644
--- a/src/opencode/types/session/form_reply_params.py
+++ b/src/opencode/types/session/form_reply_params.py
@@ -5,7 +5,7 @@
from typing_extensions import Required, Annotated, TypedDict
from ..._utils import PropertyInfo
-from ..shared.form_answer import FormAnswer
+from ..shared_params.form_answer import FormAnswer
__all__ = ["FormReplyParams"]
diff --git a/src/opencode/types/session/inbox_update_params.py b/src/opencode/types/session/inbox_update_params.py
index 38c9a3d..5b2c70d 100644
--- a/src/opencode/types/session/inbox_update_params.py
+++ b/src/opencode/types/session/inbox_update_params.py
@@ -5,8 +5,8 @@
from typing_extensions import Required, Annotated, TypedDict
from ..._utils import PropertyInfo
-from ..shared.session_id import SessionID
-from ..shared.session_inbox_delivery import SessionInboxDelivery
+from ..shared_params.session_id import SessionID
+from ..shared_params.session_inbox_delivery import SessionInboxDelivery
__all__ = ["InboxUpdateParams"]
diff --git a/src/opencode/types/session/instructions/entry_put_params.py b/src/opencode/types/session/instructions/entry_put_params.py
index f04c0ad..de28960 100644
--- a/src/opencode/types/session/instructions/entry_put_params.py
+++ b/src/opencode/types/session/instructions/entry_put_params.py
@@ -5,7 +5,7 @@
from typing_extensions import Required, Annotated, TypedDict
from ...._utils import PropertyInfo
-from ...shared.session_id import SessionID
+from ...shared_params.session_id import SessionID
__all__ = ["EntryPutParams"]
diff --git a/src/opencode/types/session/revert_stage_params.py b/src/opencode/types/session/revert_stage_params.py
index 14a6e9a..d3d6f02 100644
--- a/src/opencode/types/session/revert_stage_params.py
+++ b/src/opencode/types/session/revert_stage_params.py
@@ -6,7 +6,7 @@
from typing_extensions import Required, Annotated, TypedDict
from ..._utils import PropertyInfo
-from ..shared.session_message_id import SessionMessageID
+from ..shared_params.session_message_id import SessionMessageID
__all__ = ["RevertStageParams"]
diff --git a/src/opencode/types/session_command_params.py b/src/opencode/types/session_command_params.py
index c07e5c2..79a5dde 100644
--- a/src/opencode/types/session_command_params.py
+++ b/src/opencode/types/session_command_params.py
@@ -5,10 +5,10 @@
from typing import Iterable, Optional
from typing_extensions import Required, TypedDict
-from .shared.session_inbox_delivery import SessionInboxDelivery
-from .shared.prompt_agent_attachment import PromptAgentAttachment
-from .shared.prompt_input_file_attachment import PromptInputFileAttachment
-from .shared.prompt_input_skill_attachment import PromptInputSkillAttachment
+from .shared_params.session_inbox_delivery import SessionInboxDelivery
+from .shared_params.prompt_agent_attachment import PromptAgentAttachment
+from .shared_params.prompt_input_file_attachment import PromptInputFileAttachment
+from .shared_params.prompt_input_skill_attachment import PromptInputSkillAttachment
__all__ = ["SessionCommandParams"]
diff --git a/src/opencode/types/session_compact_params.py b/src/opencode/types/session_compact_params.py
index 4909f4f..7305b61 100644
--- a/src/opencode/types/session_compact_params.py
+++ b/src/opencode/types/session_compact_params.py
@@ -5,8 +5,8 @@
from typing import Optional
from typing_extensions import TypedDict
-from .shared.session_message_id import SessionMessageID
-from .shared.session_inbox_delivery import SessionInboxDelivery
+from .shared_params.session_message_id import SessionMessageID
+from .shared_params.session_inbox_delivery import SessionInboxDelivery
__all__ = ["SessionCompactParams"]
diff --git a/src/opencode/types/session_create_params.py b/src/opencode/types/session_create_params.py
index 5e5b053..f6d3a63 100644
--- a/src/opencode/types/session_create_params.py
+++ b/src/opencode/types/session_create_params.py
@@ -6,12 +6,12 @@
from typing_extensions import Annotated, TypedDict
from .._utils import PropertyInfo
-from .shared.agent_id import AgentID
-from .shared.model_ref import ModelRef
-from .shared.session_id import SessionID
-from .shared.session_metadata import SessionMetadata
-from .shared.permission_ruleset import PermissionRuleset
-from .shared.location_public_ref import LocationPublicRef
+from .shared_params.agent_id import AgentID
+from .shared_params.model_ref import ModelRef
+from .shared_params.session_id import SessionID
+from .shared_params.session_metadata import SessionMetadata
+from .shared_params.permission_ruleset import PermissionRuleset
+from .shared_params.location_public_ref import LocationPublicRef
__all__ = ["SessionCreateParams"]
diff --git a/src/opencode/types/session_diff_params.py b/src/opencode/types/session_diff_params.py
index 2ac4d7e..58b9ea2 100644
--- a/src/opencode/types/session_diff_params.py
+++ b/src/opencode/types/session_diff_params.py
@@ -6,7 +6,7 @@
from typing_extensions import Annotated, TypedDict
from .._utils import PropertyInfo
-from .shared.session_message_id import SessionMessageID
+from .shared_params.session_message_id import SessionMessageID
__all__ = ["SessionDiffParams"]
diff --git a/src/opencode/types/session_fork_params.py b/src/opencode/types/session_fork_params.py
index 71ae45c..cceae48 100644
--- a/src/opencode/types/session_fork_params.py
+++ b/src/opencode/types/session_fork_params.py
@@ -5,7 +5,7 @@
from typing import Optional
from typing_extensions import TypedDict
-from .shared.session_message_id import SessionMessageID
+from .shared_params.session_message_id import SessionMessageID
__all__ = ["SessionForkParams"]
diff --git a/src/opencode/types/session_import__params.py b/src/opencode/types/session_import__params.py
index 121cf2a..72705d3 100644
--- a/src/opencode/types/session_import__params.py
+++ b/src/opencode/types/session_import__params.py
@@ -5,8 +5,8 @@
from typing import Iterable, Optional
from typing_extensions import Required, TypedDict
-from .shared.session_info import SessionInfo
-from .shared.location_public_ref import LocationPublicRef
+from .shared_params.session_info import SessionInfo
+from .shared_params.location_public_ref import LocationPublicRef
__all__ = ["SessionImportParams"]
diff --git a/src/opencode/types/session_list_params.py b/src/opencode/types/session_list_params.py
index 8ac3046..b3033b5 100644
--- a/src/opencode/types/session_list_params.py
+++ b/src/opencode/types/session_list_params.py
@@ -6,11 +6,11 @@
from typing_extensions import Literal, Annotated, TypedDict
from .._utils import PropertyInfo
-from .shared.project_id import ProjectID
-from .shared.session_id import SessionID
-from .shared.absolute_path import AbsolutePath
-from .shared.relative_path import RelativePath
-from .shared.sessions_cursor import SessionsCursor
+from .shared_params.project_id import ProjectID
+from .shared_params.session_id import SessionID
+from .shared_params.absolute_path import AbsolutePath
+from .shared_params.relative_path import RelativePath
+from .shared_params.sessions_cursor import SessionsCursor
__all__ = ["SessionListParams"]
diff --git a/src/opencode/types/session_log_params.py b/src/opencode/types/session_log_params.py
index 1c81028..ceea254 100644
--- a/src/opencode/types/session_log_params.py
+++ b/src/opencode/types/session_log_params.py
@@ -5,7 +5,7 @@
from typing import Optional
from typing_extensions import TypedDict
-from .shared.event_seq import EventSeq
+from .shared_params.event_seq import EventSeq
__all__ = ["SessionLogParams"]
diff --git a/src/opencode/types/session_move_params.py b/src/opencode/types/session_move_params.py
index 2ab282e..7b7e5be 100644
--- a/src/opencode/types/session_move_params.py
+++ b/src/opencode/types/session_move_params.py
@@ -5,8 +5,8 @@
from typing import Optional
from typing_extensions import Required, TypedDict
-from .shared.absolute_path import AbsolutePath
-from .shared.session_inbox_delivery import SessionInboxDelivery
+from .shared_params.absolute_path import AbsolutePath
+from .shared_params.session_inbox_delivery import SessionInboxDelivery
__all__ = ["SessionMoveParams"]
diff --git a/src/opencode/types/session_prompt_params.py b/src/opencode/types/session_prompt_params.py
index 26e32b3..29011d8 100644
--- a/src/opencode/types/session_prompt_params.py
+++ b/src/opencode/types/session_prompt_params.py
@@ -5,11 +5,11 @@
from typing import Dict, Iterable, Optional
from typing_extensions import Required, TypedDict
-from .shared.session_message_id import SessionMessageID
-from .shared.session_inbox_delivery import SessionInboxDelivery
-from .shared.prompt_agent_attachment import PromptAgentAttachment
-from .shared.prompt_input_file_attachment import PromptInputFileAttachment
-from .shared.prompt_input_skill_attachment import PromptInputSkillAttachment
+from .shared_params.session_message_id import SessionMessageID
+from .shared_params.session_inbox_delivery import SessionInboxDelivery
+from .shared_params.prompt_agent_attachment import PromptAgentAttachment
+from .shared_params.prompt_input_file_attachment import PromptInputFileAttachment
+from .shared_params.prompt_input_skill_attachment import PromptInputSkillAttachment
__all__ = ["SessionPromptParams"]
diff --git a/src/opencode/types/session_shell_params.py b/src/opencode/types/session_shell_params.py
index 8dbd4f8..0543776 100644
--- a/src/opencode/types/session_shell_params.py
+++ b/src/opencode/types/session_shell_params.py
@@ -5,7 +5,7 @@
from typing import Optional
from typing_extensions import Required, TypedDict
-from .shared.session_message_id import SessionMessageID
+from .shared_params.session_message_id import SessionMessageID
__all__ = ["SessionShellParams"]
diff --git a/src/opencode/types/session_skill_params.py b/src/opencode/types/session_skill_params.py
index 2fe9cf7..9465de0 100644
--- a/src/opencode/types/session_skill_params.py
+++ b/src/opencode/types/session_skill_params.py
@@ -5,7 +5,7 @@
from typing import Optional
from typing_extensions import Required, TypedDict
-from .shared.skill_id import SkillID
+from .shared_params.skill_id import SkillID
__all__ = ["SessionSkillParams"]
diff --git a/src/opencode/types/session_stats_params.py b/src/opencode/types/session_stats_params.py
index 4336ea5..d1a1916 100644
--- a/src/opencode/types/session_stats_params.py
+++ b/src/opencode/types/session_stats_params.py
@@ -6,7 +6,7 @@
from typing_extensions import Literal, Annotated, TypedDict
from .._utils import PropertyInfo
-from .shared.project_id import ProjectID
+from .shared_params.project_id import ProjectID
__all__ = ["SessionStatsParams"]
diff --git a/src/opencode/types/session_switch_agent_params.py b/src/opencode/types/session_switch_agent_params.py
index 23974de..7731879 100644
--- a/src/opencode/types/session_switch_agent_params.py
+++ b/src/opencode/types/session_switch_agent_params.py
@@ -4,7 +4,7 @@
from typing_extensions import Required, TypedDict
-from .shared.agent_id import AgentID
+from .shared_params.agent_id import AgentID
__all__ = ["SessionSwitchAgentParams"]
diff --git a/src/opencode/types/session_switch_model_params.py b/src/opencode/types/session_switch_model_params.py
index e03faf1..e711cf7 100644
--- a/src/opencode/types/session_switch_model_params.py
+++ b/src/opencode/types/session_switch_model_params.py
@@ -4,7 +4,7 @@
from typing_extensions import Required, TypedDict
-from .shared.model_ref import ModelRef
+from .shared_params.model_ref import ModelRef
__all__ = ["SessionSwitchModelParams"]
diff --git a/src/opencode/types/session_synthetic_params.py b/src/opencode/types/session_synthetic_params.py
index a1b29f9..fcb2659 100644
--- a/src/opencode/types/session_synthetic_params.py
+++ b/src/opencode/types/session_synthetic_params.py
@@ -5,8 +5,8 @@
from typing import Dict, Optional
from typing_extensions import Required, TypedDict
-from .shared.session_message_id import SessionMessageID
-from .shared.session_inbox_delivery import SessionInboxDelivery
+from .shared_params.session_message_id import SessionMessageID
+from .shared_params.session_inbox_delivery import SessionInboxDelivery
__all__ = ["SessionSyntheticParams"]
diff --git a/src/opencode/types/session_update_params.py b/src/opencode/types/session_update_params.py
index 637166e..77b0e84 100644
--- a/src/opencode/types/session_update_params.py
+++ b/src/opencode/types/session_update_params.py
@@ -5,8 +5,8 @@
from typing import Optional
from typing_extensions import TypedDict
-from .shared.session_metadata import SessionMetadata
-from .shared.permission_ruleset import PermissionRuleset
+from .shared_params.session_metadata import SessionMetadata
+from .shared_params.permission_ruleset import PermissionRuleset
__all__ = ["SessionUpdateParams"]
diff --git a/src/opencode/types/shared_params/__init__.py b/src/opencode/types/shared_params/__init__.py
new file mode 100644
index 0000000..a37f929
--- /dev/null
+++ b/src/opencode/types/shared_params/__init__.py
@@ -0,0 +1,73 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .pty_id import PtyID as PtyID
+from .form_id import FormID as FormID
+from .agent_id import AgentID as AgentID
+from .shell_id import ShellID as ShellID
+from .skill_id import SkillID as SkillID
+from .vcs_mode import VcsMode as VcsMode
+from .event_seq import EventSeq as EventSeq
+from .form_when import FormWhen as FormWhen
+from .model_ref import ModelRef as ModelRef
+from .money_usd import MoneyUSD as MoneyUSD
+from .form_field import FormField as FormField
+from .form_value import FormValue as FormValue
+from .project_id import ProjectID as ProjectID
+from .session_id import SessionID as SessionID
+from .form_answer import FormAnswer as FormAnswer
+from .form_fields import FormFields as FormFields
+from .form_option import FormOption as FormOption
+from .provider_id import ProviderID as ProviderID
+from .snapshot_id import SnapshotID as SnapshotID
+from .mcp_protocol import McpProtocol as McpProtocol
+from .project_icon import ProjectIcon as ProjectIcon
+from .session_info import SessionInfo as SessionInfo
+from .absolute_path import AbsolutePath as AbsolutePath
+from .credential_id import CredentialID as CredentialID
+from .form_metadata import FormMetadata as FormMetadata
+from .permission_id import PermissionID as PermissionID
+from .relative_path import RelativePath as RelativePath
+from .web_search_id import WebSearchID as WebSearchID
+from .credential_key import CredentialKey as CredentialKey
+from .file_diff_info import FileDiffInfo as FileDiffInfo
+from .integration_id import IntegrationID as IntegrationID
+from .prompt_mention import PromptMention as PromptMention
+from .session_revert import SessionRevert as SessionRevert
+from .permission_rule import PermissionRule as PermissionRule
+from .sessions_cursor import SessionsCursor as SessionsCursor
+from .credential_value import CredentialValue as CredentialValue
+from .mcp_local_config import McpLocalConfig as McpLocalConfig
+from .permission_reply import PermissionReply as PermissionReply
+from .project_commands import ProjectCommands as ProjectCommands
+from .session_metadata import SessionMetadata as SessionMetadata
+from .token_usage_info import TokenUsageInfo as TokenUsageInfo
+from .credential_o_auth import CredentialOAuth as CredentialOAuth
+from .form_number_field import FormNumberField as FormNumberField
+from .form_string_field import FormStringField as FormStringField
+from .mcp_o_auth_config import McpOAuthConfig as McpOAuthConfig
+from .mcp_remote_config import McpRemoteConfig as McpRemoteConfig
+from .permission_effect import PermissionEffect as PermissionEffect
+from .permission_source import PermissionSource as PermissionSource
+from .form_boolean_field import FormBooleanField as FormBooleanField
+from .form_integer_field import FormIntegerField as FormIntegerField
+from .permission_ruleset import PermissionRuleset as PermissionRuleset
+from .session_message_id import SessionMessageID as SessionMessageID
+from .credential_external import CredentialExternal as CredentialExternal
+from .form_external_field import FormExternalField as FormExternalField
+from .location_public_ref import LocationPublicRef as LocationPublicRef
+from .permission_saved_id import PermissionSavedID as PermissionSavedID
+from .instruction_entry_key import InstructionEntryKey as InstructionEntryKey
+from .integration_method_id import IntegrationMethodID as IntegrationMethodID
+from .session_fork_boundary import SessionForkBoundary as SessionForkBoundary
+from .voice_transport_input import VoiceTransportInput as VoiceTransportInput
+from .form_multiselect_field import FormMultiselectField as FormMultiselectField
+from .integration_attempt_id import IntegrationAttemptID as IntegrationAttemptID
+from .session_inbox_delivery import SessionInboxDelivery as SessionInboxDelivery
+from .prompt_agent_attachment import PromptAgentAttachment as PromptAgentAttachment
+from .persistent_pty_read_lines import PersistentPtyReadLines as PersistentPtyReadLines
+from .prompt_input_file_attachment import PromptInputFileAttachment as PromptInputFileAttachment
+from .prompt_input_skill_attachment import PromptInputSkillAttachment as PromptInputSkillAttachment
+from .voice_web_rtc_transport_input import VoiceWebRTCTransportInput as VoiceWebRTCTransportInput
+from .voice_web_socket_transport_input import VoiceWebSocketTransportInput as VoiceWebSocketTransportInput
diff --git a/src/opencode/types/shared_params/absolute_path.py b/src/opencode/types/shared_params/absolute_path.py
new file mode 100644
index 0000000..2ca3947
--- /dev/null
+++ b/src/opencode/types/shared_params/absolute_path.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["AbsolutePath"]
+
+AbsolutePath: TypeAlias = str
diff --git a/src/opencode/types/shared_params/agent_id.py b/src/opencode/types/shared_params/agent_id.py
new file mode 100644
index 0000000..9bc1f18
--- /dev/null
+++ b/src/opencode/types/shared_params/agent_id.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["AgentID"]
+
+AgentID: TypeAlias = str
diff --git a/src/opencode/types/shared_params/credential_external.py b/src/opencode/types/shared_params/credential_external.py
new file mode 100644
index 0000000..0dc2b6d
--- /dev/null
+++ b/src/opencode/types/shared_params/credential_external.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from .integration_method_id import IntegrationMethodID
+
+__all__ = ["CredentialExternal"]
+
+
+class CredentialExternal( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ method_id: Required[Annotated[IntegrationMethodID, PropertyInfo(alias="methodID")]]
+
+ type: Required[Literal["external"]]
+
+ metadata: Dict[str, object]
diff --git a/src/opencode/types/shared_params/credential_id.py b/src/opencode/types/shared_params/credential_id.py
new file mode 100644
index 0000000..df5d864
--- /dev/null
+++ b/src/opencode/types/shared_params/credential_id.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["CredentialID"]
+
+CredentialID: TypeAlias = str
diff --git a/src/opencode/types/shared_params/credential_key.py b/src/opencode/types/shared_params/credential_key.py
new file mode 100644
index 0000000..8c67d4c
--- /dev/null
+++ b/src/opencode/types/shared_params/credential_key.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["CredentialKey"]
+
+
+class CredentialKey( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ key: Required[str]
+
+ type: Required[Literal["key"]]
+
+ metadata: Dict[str, object]
diff --git a/src/opencode/types/shared_params/credential_o_auth.py b/src/opencode/types/shared_params/credential_o_auth.py
new file mode 100644
index 0000000..8f242df
--- /dev/null
+++ b/src/opencode/types/shared_params/credential_o_auth.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from .integration_method_id import IntegrationMethodID
+
+__all__ = ["CredentialOAuth"]
+
+
+class CredentialOAuth( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ access: Required[str]
+
+ expires: Required[int]
+
+ method_id: Required[Annotated[IntegrationMethodID, PropertyInfo(alias="methodID")]]
+
+ refresh: Required[str]
+
+ type: Required[Literal["oauth"]]
+
+ metadata: Dict[str, object]
diff --git a/src/opencode/types/shared_params/credential_value.py b/src/opencode/types/shared_params/credential_value.py
new file mode 100644
index 0000000..87cb662
--- /dev/null
+++ b/src/opencode/types/shared_params/credential_value.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from typing_extensions import TypeAlias
+
+from .credential_key import CredentialKey
+from .credential_o_auth import CredentialOAuth
+from .credential_external import CredentialExternal
+
+__all__ = ["CredentialValue"]
+
+CredentialValue: TypeAlias = Union[CredentialOAuth, CredentialKey, CredentialExternal]
diff --git a/src/opencode/types/shared_params/event_seq.py b/src/opencode/types/shared_params/event_seq.py
new file mode 100644
index 0000000..75d6cd9
--- /dev/null
+++ b/src/opencode/types/shared_params/event_seq.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["EventSeq"]
+
+EventSeq: TypeAlias = int
diff --git a/src/opencode/types/shared_params/file_diff_info.py b/src/opencode/types/shared_params/file_diff_info.py
new file mode 100644
index 0000000..9a6c2a2
--- /dev/null
+++ b/src/opencode/types/shared_params/file_diff_info.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["FileDiffInfo"]
+
+
+class FileDiffInfo( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ additions: Required[int]
+
+ deletions: Required[int]
+
+ file: Required[str]
+
+ patch: Required[str]
+
+ status: Required[Literal["added", "deleted", "modified"]]
diff --git a/src/opencode/types/shared_params/form_answer.py b/src/opencode/types/shared_params/form_answer.py
new file mode 100644
index 0000000..5c887a3
--- /dev/null
+++ b/src/opencode/types/shared_params/form_answer.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+from .form_value import FormValue
+
+__all__ = ["FormAnswer"]
+
+FormAnswer: TypeAlias = Dict[str, FormValue]
diff --git a/src/opencode/types/shared_params/form_boolean_field.py b/src/opencode/types/shared_params/form_boolean_field.py
new file mode 100644
index 0000000..c80228a
--- /dev/null
+++ b/src/opencode/types/shared_params/form_boolean_field.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Literal, Required, TypedDict
+
+from .form_when import FormWhen
+
+__all__ = ["FormBooleanField"]
+
+
+class FormBooleanField( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ key: Required[str]
+
+ type: Required[Literal["boolean"]]
+
+ default: bool
+
+ description: str
+
+ hidden: bool
+
+ required: bool
+
+ title: str
+
+ when: Iterable[FormWhen]
diff --git a/src/opencode/types/shared_params/form_external_field.py b/src/opencode/types/shared_params/form_external_field.py
new file mode 100644
index 0000000..2517a73
--- /dev/null
+++ b/src/opencode/types/shared_params/form_external_field.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["FormExternalField"]
+
+
+class FormExternalField( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ key: Required[str]
+
+ type: Required[Literal["external"]]
+
+ url: Required[str]
+
+ description: str
+
+ title: str
diff --git a/src/opencode/types/shared_params/form_field.py b/src/opencode/types/shared_params/form_field.py
new file mode 100644
index 0000000..ddbe06f
--- /dev/null
+++ b/src/opencode/types/shared_params/form_field.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from typing_extensions import TypeAlias
+
+from .form_number_field import FormNumberField
+from .form_string_field import FormStringField
+from .form_boolean_field import FormBooleanField
+from .form_integer_field import FormIntegerField
+from .form_external_field import FormExternalField
+from .form_multiselect_field import FormMultiselectField
+
+__all__ = ["FormField"]
+
+FormField: TypeAlias = Union[
+ FormStringField, FormNumberField, FormIntegerField, FormBooleanField, FormMultiselectField, FormExternalField
+]
diff --git a/src/opencode/types/shared_params/form_fields.py b/src/opencode/types/shared_params/form_fields.py
new file mode 100644
index 0000000..114c104
--- /dev/null
+++ b/src/opencode/types/shared_params/form_fields.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import TypeAlias
+
+from .form_field import FormField
+
+__all__ = ["FormFields"]
+
+FormFields: TypeAlias = Iterable[FormField]
diff --git a/src/opencode/types/shared_params/form_id.py b/src/opencode/types/shared_params/form_id.py
new file mode 100644
index 0000000..fa51e9b
--- /dev/null
+++ b/src/opencode/types/shared_params/form_id.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["FormID"]
+
+FormID: TypeAlias = str
diff --git a/src/opencode/types/shared_params/form_integer_field.py b/src/opencode/types/shared_params/form_integer_field.py
new file mode 100644
index 0000000..81fb4b3
--- /dev/null
+++ b/src/opencode/types/shared_params/form_integer_field.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Iterable
+from typing_extensions import Literal, Required, TypedDict
+
+from .form_when import FormWhen
+
+__all__ = ["FormIntegerField"]
+
+
+class FormIntegerField( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ key: Required[str]
+
+ type: Required[Literal["integer"]]
+
+ default: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ description: str
+
+ hidden: bool
+
+ maximum: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ minimum: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ required: bool
+
+ title: str
+
+ when: Iterable[FormWhen]
diff --git a/src/opencode/types/shared_params/form_metadata.py b/src/opencode/types/shared_params/form_metadata.py
new file mode 100644
index 0000000..b60d673
--- /dev/null
+++ b/src/opencode/types/shared_params/form_metadata.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["FormMetadata"]
+
+FormMetadata: TypeAlias = Dict[str, object]
diff --git a/src/opencode/types/shared_params/form_multiselect_field.py b/src/opencode/types/shared_params/form_multiselect_field.py
new file mode 100644
index 0000000..81e0281
--- /dev/null
+++ b/src/opencode/types/shared_params/form_multiselect_field.py
@@ -0,0 +1,43 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from ..._types import SequenceNotStr
+from ..._utils import PropertyInfo
+from .form_when import FormWhen
+from .form_option import FormOption
+
+__all__ = ["FormMultiselectField"]
+
+
+class FormMultiselectField( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ key: Required[str]
+
+ options: Required[Iterable[FormOption]]
+
+ type: Required[Literal["multiselect"]]
+
+ custom: bool
+
+ default: SequenceNotStr[str]
+
+ description: str
+
+ hidden: bool
+
+ max_items: Annotated[int, PropertyInfo(alias="maxItems")]
+
+ min_items: Annotated[int, PropertyInfo(alias="minItems")]
+
+ required: bool
+
+ title: str
+
+ when: Iterable[FormWhen]
diff --git a/src/opencode/types/shared_params/form_number_field.py b/src/opencode/types/shared_params/form_number_field.py
new file mode 100644
index 0000000..e0d4a2a
--- /dev/null
+++ b/src/opencode/types/shared_params/form_number_field.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Iterable
+from typing_extensions import Literal, Required, TypedDict
+
+from .form_when import FormWhen
+
+__all__ = ["FormNumberField"]
+
+
+class FormNumberField( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ key: Required[str]
+
+ type: Required[Literal["number"]]
+
+ default: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ description: str
+
+ hidden: bool
+
+ maximum: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ minimum: Union[Literal["Infinity", "-Infinity", "NaN"], float]
+
+ required: bool
+
+ title: str
+
+ when: Iterable[FormWhen]
diff --git a/src/opencode/types/shared_params/form_option.py b/src/opencode/types/shared_params/form_option.py
new file mode 100644
index 0000000..cd8fadf
--- /dev/null
+++ b/src/opencode/types/shared_params/form_option.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["FormOption"]
+
+
+class FormOption( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ label: Required[str]
+
+ value: Required[str]
+
+ description: str
diff --git a/src/opencode/types/shared_params/form_string_field.py b/src/opencode/types/shared_params/form_string_field.py
new file mode 100644
index 0000000..9fe52d2
--- /dev/null
+++ b/src/opencode/types/shared_params/form_string_field.py
@@ -0,0 +1,48 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from .form_when import FormWhen
+from .form_option import FormOption
+
+__all__ = ["FormStringField"]
+
+
+class FormStringField( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ key: Required[str]
+
+ type: Required[Literal["string"]]
+
+ custom: bool
+
+ default: str
+
+ description: str
+
+ format: Literal["email", "uri", "date", "date-time"]
+
+ hidden: bool
+
+ max_length: Annotated[int, PropertyInfo(alias="maxLength")]
+
+ min_length: Annotated[int, PropertyInfo(alias="minLength")]
+
+ options: Iterable[FormOption]
+
+ pattern: str
+
+ placeholder: str
+
+ required: bool
+
+ title: str
+
+ when: Iterable[FormWhen]
diff --git a/src/opencode/types/shared_params/form_value.py b/src/opencode/types/shared_params/form_value.py
new file mode 100644
index 0000000..922b388
--- /dev/null
+++ b/src/opencode/types/shared_params/form_value.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from typing_extensions import Literal, TypeAlias
+
+from ..._types import SequenceNotStr
+
+__all__ = ["FormValue"]
+
+FormValue: TypeAlias = Union[Literal["Infinity", "-Infinity", "NaN"], str, float, bool, SequenceNotStr[str]]
diff --git a/src/opencode/types/shared_params/form_when.py b/src/opencode/types/shared_params/form_when.py
new file mode 100644
index 0000000..eb815a4
--- /dev/null
+++ b/src/opencode/types/shared_params/form_when.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["FormWhen"]
+
+
+class FormWhen( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ key: Required[str]
+
+ op: Required[Literal["eq", "neq"]]
+
+ value: Required[Union[Literal["Infinity", "-Infinity", "NaN"], str, float, bool]]
diff --git a/src/opencode/types/shared_params/instruction_entry_key.py b/src/opencode/types/shared_params/instruction_entry_key.py
new file mode 100644
index 0000000..d76969c
--- /dev/null
+++ b/src/opencode/types/shared_params/instruction_entry_key.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["InstructionEntryKey"]
+
+InstructionEntryKey: TypeAlias = str
diff --git a/src/opencode/types/shared_params/integration_attempt_id.py b/src/opencode/types/shared_params/integration_attempt_id.py
new file mode 100644
index 0000000..8d1bcd1
--- /dev/null
+++ b/src/opencode/types/shared_params/integration_attempt_id.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["IntegrationAttemptID"]
+
+IntegrationAttemptID: TypeAlias = str
diff --git a/src/opencode/types/shared_params/integration_id.py b/src/opencode/types/shared_params/integration_id.py
new file mode 100644
index 0000000..b6fcd09
--- /dev/null
+++ b/src/opencode/types/shared_params/integration_id.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["IntegrationID"]
+
+IntegrationID: TypeAlias = str
diff --git a/src/opencode/types/shared_params/integration_method_id.py b/src/opencode/types/shared_params/integration_method_id.py
new file mode 100644
index 0000000..16d5a05
--- /dev/null
+++ b/src/opencode/types/shared_params/integration_method_id.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["IntegrationMethodID"]
+
+IntegrationMethodID: TypeAlias = str
diff --git a/src/opencode/types/shared_params/location_public_ref.py b/src/opencode/types/shared_params/location_public_ref.py
new file mode 100644
index 0000000..f5f1a43
--- /dev/null
+++ b/src/opencode/types/shared_params/location_public_ref.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+from .absolute_path import AbsolutePath
+
+__all__ = ["LocationPublicRef"]
+
+
+class LocationPublicRef( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ directory: Required[AbsolutePath]
diff --git a/src/opencode/types/shared_params/mcp_local_config.py b/src/opencode/types/shared_params/mcp_local_config.py
new file mode 100644
index 0000000..aa2548b
--- /dev/null
+++ b/src/opencode/types/shared_params/mcp_local_config.py
@@ -0,0 +1,52 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict
+from typing_extensions import Literal, Required, TypedDict
+
+from ..._types import SequenceNotStr
+from .mcp_protocol import McpProtocol
+
+__all__ = ["McpLocalConfig", "Timeout"]
+
+
+class Timeout( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ catalog: int
+
+ execution: int
+
+ startup: int
+
+
+class McpLocalConfig( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ command: Required[SequenceNotStr[str]]
+
+ type: Required[Literal["local"]]
+
+ codemode: bool
+
+ cwd: str
+
+ disabled: bool
+
+ environment: Dict[str, str]
+
+ protocol: McpProtocol
+ """MCP protocol negotiation.
+
+ "legacy" (default) opens with the initialize handshake and speaks protocol
+ revisions up to 2025-11-25. "auto" probes for the 2026-07-28 revision and falls
+ back to legacy when the server does not support it. "2026-07-28" requires that
+ revision and fails otherwise.
+ """
+
+ timeout: Timeout
diff --git a/src/opencode/types/shared_params/mcp_o_auth_config.py b/src/opencode/types/shared_params/mcp_o_auth_config.py
new file mode 100644
index 0000000..d064ef9
--- /dev/null
+++ b/src/opencode/types/shared_params/mcp_o_auth_config.py
@@ -0,0 +1,25 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["McpOAuthConfig"]
+
+
+class McpOAuthConfig( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ auth_server_metadata_url: str
+
+ callback_port: int
+
+ client_id: str
+
+ client_secret: str
+
+ redirect_uri: str
+
+ scope: str
diff --git a/src/opencode/types/shared_params/mcp_protocol.py b/src/opencode/types/shared_params/mcp_protocol.py
new file mode 100644
index 0000000..7edf026
--- /dev/null
+++ b/src/opencode/types/shared_params/mcp_protocol.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["McpProtocol"]
+
+McpProtocol: TypeAlias = Literal["legacy", "auto", "2026-07-28"]
diff --git a/src/opencode/types/shared_params/mcp_remote_config.py b/src/opencode/types/shared_params/mcp_remote_config.py
new file mode 100644
index 0000000..628ed84
--- /dev/null
+++ b/src/opencode/types/shared_params/mcp_remote_config.py
@@ -0,0 +1,54 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union
+from typing_extensions import Literal, Required, TypeAlias, TypedDict
+
+from .mcp_protocol import McpProtocol
+from .mcp_o_auth_config import McpOAuthConfig
+
+__all__ = ["McpRemoteConfig", "Oauth", "Timeout"]
+
+Oauth: TypeAlias = Union[Literal[False], McpOAuthConfig]
+
+
+class Timeout( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ catalog: int
+
+ execution: int
+
+ startup: int
+
+
+class McpRemoteConfig( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ type: Required[Literal["remote"]]
+
+ url: Required[str]
+
+ codemode: bool
+
+ disabled: bool
+
+ headers: Dict[str, str]
+
+ oauth: Oauth
+
+ protocol: McpProtocol
+ """MCP protocol negotiation.
+
+ "legacy" (default) opens with the initialize handshake and speaks protocol
+ revisions up to 2025-11-25. "auto" probes for the 2026-07-28 revision and falls
+ back to legacy when the server does not support it. "2026-07-28" requires that
+ revision and fails otherwise.
+ """
+
+ timeout: Timeout
diff --git a/src/opencode/types/shared_params/model_ref.py b/src/opencode/types/shared_params/model_ref.py
new file mode 100644
index 0000000..5afcf95
--- /dev/null
+++ b/src/opencode/types/shared_params/model_ref.py
@@ -0,0 +1,22 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from .provider_id import ProviderID
+
+__all__ = ["ModelRef"]
+
+
+class ModelRef( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ id: Required[str]
+
+ provider_id: Required[Annotated[ProviderID, PropertyInfo(alias="providerID")]]
+
+ variant: str
diff --git a/src/opencode/types/shared_params/money_usd.py b/src/opencode/types/shared_params/money_usd.py
new file mode 100644
index 0000000..cf38c4a
--- /dev/null
+++ b/src/opencode/types/shared_params/money_usd.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["MoneyUSD"]
+
+MoneyUSD: TypeAlias = float
diff --git a/src/opencode/types/shared_params/permission_effect.py b/src/opencode/types/shared_params/permission_effect.py
new file mode 100644
index 0000000..94d2326
--- /dev/null
+++ b/src/opencode/types/shared_params/permission_effect.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["PermissionEffect"]
+
+PermissionEffect: TypeAlias = Literal["allow", "deny", "ask"]
diff --git a/src/opencode/types/shared_params/permission_id.py b/src/opencode/types/shared_params/permission_id.py
new file mode 100644
index 0000000..61b5e1b
--- /dev/null
+++ b/src/opencode/types/shared_params/permission_id.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["PermissionID"]
+
+PermissionID: TypeAlias = str
diff --git a/src/opencode/types/shared_params/permission_reply.py b/src/opencode/types/shared_params/permission_reply.py
new file mode 100644
index 0000000..e8d1206
--- /dev/null
+++ b/src/opencode/types/shared_params/permission_reply.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["PermissionReply"]
+
+PermissionReply: TypeAlias = Literal["once", "always", "reject"]
diff --git a/src/opencode/types/shared_params/permission_rule.py b/src/opencode/types/shared_params/permission_rule.py
new file mode 100644
index 0000000..76cff16
--- /dev/null
+++ b/src/opencode/types/shared_params/permission_rule.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+from .permission_effect import PermissionEffect
+
+__all__ = ["PermissionRule"]
+
+
+class PermissionRule( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ action: Required[str]
+
+ effect: Required[PermissionEffect]
+
+ resource: Required[str]
diff --git a/src/opencode/types/shared_params/permission_ruleset.py b/src/opencode/types/shared_params/permission_ruleset.py
new file mode 100644
index 0000000..b027a0e
--- /dev/null
+++ b/src/opencode/types/shared_params/permission_ruleset.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import TypeAlias
+
+from .permission_rule import PermissionRule
+
+__all__ = ["PermissionRuleset"]
+
+PermissionRuleset: TypeAlias = Iterable[PermissionRule]
diff --git a/src/opencode/types/shared_params/permission_saved_id.py b/src/opencode/types/shared_params/permission_saved_id.py
new file mode 100644
index 0000000..0bc7b3e
--- /dev/null
+++ b/src/opencode/types/shared_params/permission_saved_id.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["PermissionSavedID"]
+
+PermissionSavedID: TypeAlias = str
diff --git a/src/opencode/types/shared_params/permission_source.py b/src/opencode/types/shared_params/permission_source.py
new file mode 100644
index 0000000..a846e04
--- /dev/null
+++ b/src/opencode/types/shared_params/permission_source.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["PermissionSource"]
+
+
+class PermissionSource( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ id: Required[str]
+
+ message_id: Required[Annotated[str, PropertyInfo(alias="messageID")]]
+
+ type: Required[Literal["tool"]]
diff --git a/src/opencode/types/shared_params/persistent_pty_read_lines.py b/src/opencode/types/shared_params/persistent_pty_read_lines.py
new file mode 100644
index 0000000..0d60d32
--- /dev/null
+++ b/src/opencode/types/shared_params/persistent_pty_read_lines.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["PersistentPtyReadLines"]
+
+PersistentPtyReadLines: TypeAlias = int
diff --git a/src/opencode/types/shared_params/project_commands.py b/src/opencode/types/shared_params/project_commands.py
new file mode 100644
index 0000000..32cba6d
--- /dev/null
+++ b/src/opencode/types/shared_params/project_commands.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["ProjectCommands"]
+
+
+class ProjectCommands( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ start: str
+ """Startup script to run when creating a new workspace (worktree)"""
diff --git a/src/opencode/types/shared_params/project_icon.py b/src/opencode/types/shared_params/project_icon.py
new file mode 100644
index 0000000..7d5f9d0
--- /dev/null
+++ b/src/opencode/types/shared_params/project_icon.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["ProjectIcon"]
+
+
+class ProjectIcon( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ color: str
+
+ override: str
+
+ url: str
diff --git a/src/opencode/types/shared_params/project_id.py b/src/opencode/types/shared_params/project_id.py
new file mode 100644
index 0000000..d8fbd5b
--- /dev/null
+++ b/src/opencode/types/shared_params/project_id.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["ProjectID"]
+
+ProjectID: TypeAlias = str
diff --git a/src/opencode/types/shared_params/prompt_agent_attachment.py b/src/opencode/types/shared_params/prompt_agent_attachment.py
new file mode 100644
index 0000000..79e87cb
--- /dev/null
+++ b/src/opencode/types/shared_params/prompt_agent_attachment.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+from .prompt_mention import PromptMention
+
+__all__ = ["PromptAgentAttachment"]
+
+
+class PromptAgentAttachment( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ name: Required[str]
+
+ mention: PromptMention
diff --git a/src/opencode/types/shared_params/prompt_input_file_attachment.py b/src/opencode/types/shared_params/prompt_input_file_attachment.py
new file mode 100644
index 0000000..1caf93a
--- /dev/null
+++ b/src/opencode/types/shared_params/prompt_input_file_attachment.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+from .prompt_mention import PromptMention
+
+__all__ = ["PromptInputFileAttachment"]
+
+
+class PromptInputFileAttachment( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ uri: Required[str]
+
+ description: str
+
+ mention: PromptMention
+
+ name: str
diff --git a/src/opencode/types/shared_params/prompt_input_skill_attachment.py b/src/opencode/types/shared_params/prompt_input_skill_attachment.py
new file mode 100644
index 0000000..fce120b
--- /dev/null
+++ b/src/opencode/types/shared_params/prompt_input_skill_attachment.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+from .skill_id import SkillID
+from .prompt_mention import PromptMention
+
+__all__ = ["PromptInputSkillAttachment"]
+
+
+class PromptInputSkillAttachment( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ id: Required[SkillID]
+
+ mention: PromptMention
diff --git a/src/opencode/types/shared_params/prompt_mention.py b/src/opencode/types/shared_params/prompt_mention.py
new file mode 100644
index 0000000..9ddf49a
--- /dev/null
+++ b/src/opencode/types/shared_params/prompt_mention.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["PromptMention"]
+
+
+class PromptMention( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ end: Required[float]
+
+ start: Required[float]
+
+ text: Required[str]
diff --git a/src/opencode/types/shared_params/provider_id.py b/src/opencode/types/shared_params/provider_id.py
new file mode 100644
index 0000000..20f55b0
--- /dev/null
+++ b/src/opencode/types/shared_params/provider_id.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["ProviderID"]
+
+ProviderID: TypeAlias = str
diff --git a/src/opencode/types/shared_params/pty_id.py b/src/opencode/types/shared_params/pty_id.py
new file mode 100644
index 0000000..b771b73
--- /dev/null
+++ b/src/opencode/types/shared_params/pty_id.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["PtyID"]
+
+PtyID: TypeAlias = str
diff --git a/src/opencode/types/shared_params/relative_path.py b/src/opencode/types/shared_params/relative_path.py
new file mode 100644
index 0000000..5950ab1
--- /dev/null
+++ b/src/opencode/types/shared_params/relative_path.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["RelativePath"]
+
+RelativePath: TypeAlias = str
diff --git a/src/opencode/types/shared_params/session_fork_boundary.py b/src/opencode/types/shared_params/session_fork_boundary.py
new file mode 100644
index 0000000..6116c17
--- /dev/null
+++ b/src/opencode/types/shared_params/session_fork_boundary.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict
+
+from ..._utils import PropertyInfo
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionForkBoundary", "SessionForkBoundaryVariant0", "SessionForkBoundaryVariant1"]
+
+
+class SessionForkBoundaryVariant0( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ message_id: Required[Annotated[SessionMessageID, PropertyInfo(alias="messageID")]]
+
+ type: Required[Literal["before"]]
+
+
+class SessionForkBoundaryVariant1( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ message_id: Required[Annotated[SessionMessageID, PropertyInfo(alias="messageID")]]
+
+ type: Required[Literal["through"]]
+
+
+SessionForkBoundary: TypeAlias = Union[SessionForkBoundaryVariant0, SessionForkBoundaryVariant1]
diff --git a/src/opencode/types/shared_params/session_id.py b/src/opencode/types/shared_params/session_id.py
new file mode 100644
index 0000000..236cf0f
--- /dev/null
+++ b/src/opencode/types/shared_params/session_id.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionID"]
+
+SessionID: TypeAlias = str
diff --git a/src/opencode/types/shared_params/session_inbox_delivery.py b/src/opencode/types/shared_params/session_inbox_delivery.py
new file mode 100644
index 0000000..a210bd0
--- /dev/null
+++ b/src/opencode/types/shared_params/session_inbox_delivery.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["SessionInboxDelivery"]
+
+SessionInboxDelivery: TypeAlias = Literal["steer", "queue"]
diff --git a/src/opencode/types/shared_params/session_info.py b/src/opencode/types/shared_params/session_info.py
new file mode 100644
index 0000000..9bd49e7
--- /dev/null
+++ b/src/opencode/types/shared_params/session_info.py
@@ -0,0 +1,85 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from .agent_id import AgentID
+from .model_ref import ModelRef
+from .money_usd import MoneyUSD
+from .project_id import ProjectID
+from .session_id import SessionID
+from .relative_path import RelativePath
+from .session_revert import SessionRevert
+from .session_metadata import SessionMetadata
+from .token_usage_info import TokenUsageInfo
+from .permission_ruleset import PermissionRuleset
+from .location_public_ref import LocationPublicRef
+from .session_fork_boundary import SessionForkBoundary
+
+__all__ = ["SessionInfo", "Time", "Fork"]
+
+
+class Time( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ created: Required[float]
+
+ updated: Required[float]
+
+ archived: float
+
+ idle: float
+
+ viewed: float
+
+
+class Fork( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ boundary: Required[SessionForkBoundary]
+
+ session_id: Required[Annotated[SessionID, PropertyInfo(alias="sessionID")]]
+
+
+class SessionInfo( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ id: Required[SessionID]
+
+ cost: Required[MoneyUSD]
+
+ location: Required[LocationPublicRef]
+
+ project_id: Required[Annotated[ProjectID, PropertyInfo(alias="projectID")]]
+
+ time: Required[Time]
+
+ tokens: Required[TokenUsageInfo]
+
+ agent: AgentID
+
+ fork: Fork
+
+ metadata: SessionMetadata
+
+ model: ModelRef
+
+ outcome: Literal["succeeded", "failed", "interrupted"]
+
+ parent_id: Annotated[SessionID, PropertyInfo(alias="parentID")]
+
+ permissions: PermissionRuleset
+
+ revert: SessionRevert
+
+ subpath: RelativePath
+
+ title: str
diff --git a/src/opencode/types/shared_params/session_message_id.py b/src/opencode/types/shared_params/session_message_id.py
new file mode 100644
index 0000000..3736089
--- /dev/null
+++ b/src/opencode/types/shared_params/session_message_id.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionMessageID"]
+
+SessionMessageID: TypeAlias = str
diff --git a/src/opencode/types/shared_params/session_metadata.py b/src/opencode/types/shared_params/session_metadata.py
new file mode 100644
index 0000000..6aaabb8
--- /dev/null
+++ b/src/opencode/types/shared_params/session_metadata.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionMetadata"]
+
+SessionMetadata: TypeAlias = Dict[str, object]
diff --git a/src/opencode/types/shared_params/session_revert.py b/src/opencode/types/shared_params/session_revert.py
new file mode 100644
index 0000000..f733556
--- /dev/null
+++ b/src/opencode/types/shared_params/session_revert.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+from .snapshot_id import SnapshotID
+from .file_diff_info import FileDiffInfo
+from .session_message_id import SessionMessageID
+
+__all__ = ["SessionRevert"]
+
+
+class SessionRevert( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ message_id: Required[Annotated[SessionMessageID, PropertyInfo(alias="messageID")]]
+
+ files: Iterable[FileDiffInfo]
+
+ part_id: Annotated[str, PropertyInfo(alias="partID")]
+
+ snapshot: SnapshotID
diff --git a/src/opencode/types/shared_params/sessions_cursor.py b/src/opencode/types/shared_params/sessions_cursor.py
new file mode 100644
index 0000000..76e7e07
--- /dev/null
+++ b/src/opencode/types/shared_params/sessions_cursor.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["SessionsCursor"]
+
+SessionsCursor: TypeAlias = str
diff --git a/src/opencode/types/shared_params/shell_id.py b/src/opencode/types/shared_params/shell_id.py
new file mode 100644
index 0000000..26c0f01
--- /dev/null
+++ b/src/opencode/types/shared_params/shell_id.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["ShellID"]
+
+ShellID: TypeAlias = str
diff --git a/src/opencode/types/shared_params/skill_id.py b/src/opencode/types/shared_params/skill_id.py
new file mode 100644
index 0000000..7573fbb
--- /dev/null
+++ b/src/opencode/types/shared_params/skill_id.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["SkillID"]
+
+SkillID: TypeAlias = str
diff --git a/src/opencode/types/shared_params/snapshot_id.py b/src/opencode/types/shared_params/snapshot_id.py
new file mode 100644
index 0000000..cd5aef3
--- /dev/null
+++ b/src/opencode/types/shared_params/snapshot_id.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["SnapshotID"]
+
+SnapshotID: TypeAlias = str
diff --git a/src/opencode/types/shared_params/token_usage_info.py b/src/opencode/types/shared_params/token_usage_info.py
new file mode 100644
index 0000000..6f43902
--- /dev/null
+++ b/src/opencode/types/shared_params/token_usage_info.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["TokenUsageInfo", "Cache"]
+
+
+class Cache( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ read: Required[float]
+
+ write: Required[float]
+
+
+class TokenUsageInfo( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ cache: Required[Cache]
+
+ input: Required[float]
+
+ output: Required[float]
+
+ reasoning: Required[float]
diff --git a/src/opencode/types/shared_params/vcs_mode.py b/src/opencode/types/shared_params/vcs_mode.py
new file mode 100644
index 0000000..b1cdb86
--- /dev/null
+++ b/src/opencode/types/shared_params/vcs_mode.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypeAlias
+
+__all__ = ["VcsMode"]
+
+VcsMode: TypeAlias = Literal["working", "branch", "committed"]
diff --git a/src/opencode/types/shared_params/voice_transport_input.py b/src/opencode/types/shared_params/voice_transport_input.py
new file mode 100644
index 0000000..c7b02d9
--- /dev/null
+++ b/src/opencode/types/shared_params/voice_transport_input.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from typing_extensions import TypeAlias
+
+from .voice_web_rtc_transport_input import VoiceWebRTCTransportInput
+from .voice_web_socket_transport_input import VoiceWebSocketTransportInput
+
+__all__ = ["VoiceTransportInput"]
+
+VoiceTransportInput: TypeAlias = Union[VoiceWebRTCTransportInput, VoiceWebSocketTransportInput]
diff --git a/src/opencode/types/shared_params/voice_web_rtc_transport_input.py b/src/opencode/types/shared_params/voice_web_rtc_transport_input.py
new file mode 100644
index 0000000..b002f56
--- /dev/null
+++ b/src/opencode/types/shared_params/voice_web_rtc_transport_input.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["VoiceWebRTCTransportInput"]
+
+
+class VoiceWebRTCTransportInput( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ sdp: Required[str]
+
+ type: Required[Literal["webrtc"]]
+
+ dcid: Union[Literal["Infinity", "-Infinity", "NaN"], float, None]
diff --git a/src/opencode/types/shared_params/voice_web_socket_transport_input.py b/src/opencode/types/shared_params/voice_web_socket_transport_input.py
new file mode 100644
index 0000000..e12fe6e
--- /dev/null
+++ b/src/opencode/types/shared_params/voice_web_socket_transport_input.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["VoiceWebSocketTransportInput"]
+
+
+class VoiceWebSocketTransportInput( # type: ignore[call-arg]
+ TypedDict,
+ total=False,
+ extra_items=object, # pyright: ignore[reportGeneralTypeIssues]
+):
+ type: Required[Literal["websocket"]]
diff --git a/src/opencode/types/shared_params/web_search_id.py b/src/opencode/types/shared_params/web_search_id.py
new file mode 100644
index 0000000..1e4ca4e
--- /dev/null
+++ b/src/opencode/types/shared_params/web_search_id.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypeAlias
+
+__all__ = ["WebSearchID"]
+
+WebSearchID: TypeAlias = str
diff --git a/src/opencode/types/vc_diff_params.py b/src/opencode/types/vc_diff_params.py
index 7105c5f..a28fb3b 100644
--- a/src/opencode/types/vc_diff_params.py
+++ b/src/opencode/types/vc_diff_params.py
@@ -5,7 +5,7 @@
from typing import Dict, Union, Optional
from typing_extensions import Required, TypeAlias, TypedDict
-from .shared.vcs_mode import VcsMode
+from .shared_params.vcs_mode import VcsMode
__all__ = ["VcDiffParams", "Location"]
diff --git a/src/opencode/types/voice_session_params.py b/src/opencode/types/voice_session_params.py
index 2478f88..2d77819 100644
--- a/src/opencode/types/voice_session_params.py
+++ b/src/opencode/types/voice_session_params.py
@@ -5,7 +5,7 @@
from typing import Dict, Optional
from typing_extensions import Required, TypedDict
-from .shared.voice_transport_input import VoiceTransportInput
+from .shared_params.voice_transport_input import VoiceTransportInput
__all__ = ["VoiceSessionParams"]
diff --git a/src/opencode/types/websearch_query_params.py b/src/opencode/types/websearch_query_params.py
index 272dc51..c86c6a1 100644
--- a/src/opencode/types/websearch_query_params.py
+++ b/src/opencode/types/websearch_query_params.py
@@ -6,7 +6,7 @@
from typing_extensions import Required, Annotated, TypeAlias, TypedDict
from .._utils import PropertyInfo
-from .shared.web_search_id import WebSearchID
+from .shared_params.web_search_id import WebSearchID
__all__ = ["WebsearchQueryParams", "Location"]
diff --git a/src/opencode/types/worktree_create_params.py b/src/opencode/types/worktree_create_params.py
index bc49eee..a22d355 100644
--- a/src/opencode/types/worktree_create_params.py
+++ b/src/opencode/types/worktree_create_params.py
@@ -5,8 +5,8 @@
from typing_extensions import Required, Annotated, TypedDict
from .._utils import PropertyInfo
-from .shared.project_id import ProjectID
-from .shared.absolute_path import AbsolutePath
+from .shared_params.project_id import ProjectID
+from .shared_params.absolute_path import AbsolutePath
__all__ = ["WorktreeCreateParams"]
diff --git a/src/opencode/types/worktree_list_params.py b/src/opencode/types/worktree_list_params.py
index 1c991b8..d43a961 100644
--- a/src/opencode/types/worktree_list_params.py
+++ b/src/opencode/types/worktree_list_params.py
@@ -5,7 +5,7 @@
from typing_extensions import Required, Annotated, TypedDict
from .._utils import PropertyInfo
-from .shared.project_id import ProjectID
+from .shared_params.project_id import ProjectID
__all__ = ["WorktreeListParams"]
diff --git a/src/opencode/types/worktree_refresh_params.py b/src/opencode/types/worktree_refresh_params.py
index 5e74a2a..de2c4e7 100644
--- a/src/opencode/types/worktree_refresh_params.py
+++ b/src/opencode/types/worktree_refresh_params.py
@@ -5,7 +5,7 @@
from typing_extensions import Required, Annotated, TypedDict
from .._utils import PropertyInfo
-from .shared.project_id import ProjectID
+from .shared_params.project_id import ProjectID
__all__ = ["WorktreeRefreshParams"]
diff --git a/src/opencode/types/worktree_remove_params.py b/src/opencode/types/worktree_remove_params.py
index 743fcc4..2c5a1d3 100644
--- a/src/opencode/types/worktree_remove_params.py
+++ b/src/opencode/types/worktree_remove_params.py
@@ -5,8 +5,8 @@
from typing_extensions import Required, Annotated, TypedDict
from .._utils import PropertyInfo
-from .shared.project_id import ProjectID
-from .shared.absolute_path import AbsolutePath
+from .shared_params.project_id import ProjectID
+from .shared_params.absolute_path import AbsolutePath
__all__ = ["WorktreeRemoveParams"]
From bf2fc4a0409663f8f1c6b85b15e23bcdfbe8a82a Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 16:55:16 +0530
Subject: [PATCH 12/41] Adapt the feature tests and modules to the generated
API
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
pyproject.toml | 7 ++++++-
src/opencode/_events.py | 34 +++++++++++++++++++++++++++++++--
src/opencode/lib/_session.py | 3 ++-
src/opencode/lib/_support.py | 10 +++++++---
tests/custom/test_events.py | 4 ++--
tests/custom/test_helpers.py | 6 ++++--
tests/custom/test_live.py | 3 ++-
tests/custom/test_pagination.py | 9 +++++----
tests/custom/test_rpc.py | 2 +-
9 files changed, 61 insertions(+), 17 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 6def56d..2943ccb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -91,8 +91,13 @@ include = ["src/opencode", "tests", "README.md", "LICENSE", "CHANGELOG.md", "api
required-version = ">=0.8"
[tool.pytest.ini_options]
+# `pytest` runs the generated suite (pytest-asyncio). The hand-written tests in tests/custom use
+# anyio and trio, so they run apart, with pytest-asyncio strict and without the generated conftest:
+# pytest --confcutdir=tests/custom -o asyncio_mode=strict -o addopts="-ra --strict-markers" tests/custom
testpaths = ["tests"]
-addopts = ["-ra", "--strict-markers", "--strict-config"]
+addopts = ["-ra", "--strict-markers", "--strict-config", "--ignore=tests/custom"]
+asyncio_mode = "auto"
+asyncio_default_fixture_loop_scope = "session"
xfail_strict = true
filterwarnings = ["error"]
markers = [
diff --git a/src/opencode/_events.py b/src/opencode/_events.py
index c7c00c0..4ae9e03 100644
--- a/src/opencode/_events.py
+++ b/src/opencode/_events.py
@@ -140,6 +140,32 @@ def _accepts(self, event: object) -> bool:
_End = anyio.EndOfStream
+def _watch_activity(stream: Stream[Event], on_chunk: Callable[[], object]) -> None:
+ """Calls ``on_chunk`` for every chunk the stream receives, keepalive comments included."""
+ response = stream.response
+ chunks = response.iter_bytes
+
+ def iter_bytes(chunk_size: int | None = None) -> Iterator[bytes]:
+ for chunk in chunks(chunk_size):
+ on_chunk()
+ yield chunk
+
+ response.iter_bytes = iter_bytes # type: ignore[method-assign]
+
+
+def _async_watch_activity(stream: AsyncStream[Event], on_chunk: Callable[[], object]) -> None:
+ """The async form of :func:`_watch_activity`."""
+ response = stream.response
+ chunks = response.aiter_bytes
+
+ async def aiter_bytes(chunk_size: int | None = None) -> AsyncIterator[bytes]:
+ async for chunk in chunks(chunk_size):
+ on_chunk()
+ yield chunk
+
+ response.aiter_bytes = aiter_bytes # type: ignore[method-assign]
+
+
def _interrupt(stream: Stream[Event]) -> None:
"""Unblocks a read in progress on another thread, which then ends with :class:`APIConnectionError`.
@@ -324,7 +350,7 @@ def _read(self, connection: _SyncConnection) -> None:
try:
if stopped:
return
- stream.on_activity = connection.activity
+ _watch_activity(stream, connection.activity)
# A fresh connection replays nothing yet; reconnects must not repeat server.connected.
seen_connected = connection.connected is not None
for event in stream:
@@ -339,6 +365,8 @@ def _read(self, connection: _SyncConnection) -> None:
self._publish(connection, event)
finally:
connection.stream = None
+ # End the iteration first, so the response's byte stream is closed with it.
+ stream._iterator.close() # pyright: ignore[reportPrivateUsage, reportAttributeAccessIssue]
stream.close()
@@ -624,7 +652,7 @@ async def _read(self, connection: _AsyncConnection) -> None:
stream: AsyncStream[Event] = await self._client.event.subscribe()
try:
- stream.on_activity = connection.activity
+ _async_watch_activity(stream, connection.activity)
seen_connected = connection.connected is not None
async for event in stream:
connection.delivered = True
@@ -636,6 +664,8 @@ async def _read(self, connection: _AsyncConnection) -> None:
self._publish(connection, event)
finally:
with anyio.CancelScope(shield=True):
+ # End the iteration first, so the response's byte stream is closed with it.
+ await stream._iterator.aclose() # pyright: ignore[reportPrivateUsage, reportAttributeAccessIssue]
await stream.close()
diff --git a/src/opencode/lib/_session.py b/src/opencode/lib/_session.py
index 798a2b4..77cd8c0 100644
--- a/src/opencode/lib/_session.py
+++ b/src/opencode/lib/_session.py
@@ -89,7 +89,8 @@ def cost(self) -> float:
@property
def tokens(self) -> TokenUsageInfo:
"""Token usage of this run, summed over its assistant messages."""
- from ..types import TokenUsageInfo, TokenUsageInfoCache
+ from ..types import TokenUsageInfo
+ from ..types.shared.token_usage_info import Cache as TokenUsageInfoCache
total = TokenUsageInfo(input=0, output=0, reasoning=0, cache=TokenUsageInfoCache(read=0, write=0))
for message in self.assistant_messages:
diff --git a/src/opencode/lib/_support.py b/src/opencode/lib/_support.py
index 8ee461b..dc2ce50 100644
--- a/src/opencode/lib/_support.py
+++ b/src/opencode/lib/_support.py
@@ -12,7 +12,9 @@
import enum
import functools
from collections.abc import Mapping, Sequence
-from typing import Any, Dict, Union, cast
+from typing import Any, Optional, Union, cast
+
+from typing_extensions import TypedDict
from urllib.parse import quote
import httpx
@@ -36,8 +38,10 @@
DEFAULT_USERNAME = "opencode"
"""The Basic auth user name the OpenCode server expects."""
-Location = Dict[str, object]
-"""Where a request runs on the server, for example ``Location(directory="/repo")``."""
+class Location(TypedDict, total=False):
+ """Where a request runs on the server, for example ``Location(directory="/repo")``."""
+
+ directory: Optional[str]
Timeout = Union[float, httpx.Timeout, None]
"""A timeout in seconds, an ``httpx.Timeout``, or ``None`` for none."""
diff --git a/tests/custom/test_events.py b/tests/custom/test_events.py
index f9b88e3..1e85c89 100644
--- a/tests/custom/test_events.py
+++ b/tests/custom/test_events.py
@@ -267,14 +267,14 @@ def test_reconnect_dedupes_server_connected(client: Opencode, mock_api: respx.Mo
def test_reconnect_gives_up_after_max_attempts(client: Opencode, mock_api: respx.MockRouter) -> None:
route = mock_api.get("/api/event").mock(side_effect=httpx.ConnectError("refused"))
hub = EventHub(client.with_options(max_retries=0), reconnect=Reconnect(initial_delay=0, max_attempts=2))
- with pytest.raises(Exception, match="refused"), hub.subscribe() as events:
+ with pytest.raises(Exception, match="Connection error"), hub.subscribe() as events:
next(events)
assert route.call_count == 3
def test_without_reconnect_errors_end_every_subscription(client: Opencode, mock_api: respx.MockRouter) -> None:
mock_api.get("/api/event").mock(side_effect=httpx.ConnectError("refused"))
- with pytest.raises(Exception, match="refused"), client.with_options(max_retries=0).events.subscribe() as events:
+ with pytest.raises(Exception, match="Connection error"), client.with_options(max_retries=0).events.subscribe() as events:
next(events)
diff --git a/tests/custom/test_helpers.py b/tests/custom/test_helpers.py
index b37bede..ac60837 100644
--- a/tests/custom/test_helpers.py
+++ b/tests/custom/test_helpers.py
@@ -17,6 +17,7 @@
)
from opencode.lib import RunResult
from opencode.testing import FakeServer
+from opencode.types.shared_params import ModelRef as ModelRefParam
from opencode.types import ModelRef, ServerConnectedEvent, SessionCreatedEvent, SessionIdleEvent
@@ -35,7 +36,7 @@ def _paths(fake: FakeServer) -> list[str]:
def test_run_creates_session_and_returns_result() -> None:
fake = FakeServer(reply=lambda text: f"echo: {text}", cost=0.5, tokens=(100, 20))
client = Opencode.fake(fake)
- model = ModelRef.model_validate({"id": "claude", "providerID": "anthropic"})
+ model: ModelRefParam = {"id": "claude", "provider_id": "anthropic"}
result = client.session.run("hello", agent="build", model=model)
@@ -46,7 +47,8 @@ def test_run_creates_session_and_returns_result() -> None:
assert result.cost == 0.5
assert (result.tokens.input, result.tokens.output) == (100, 20)
assert result.session.agent == "build"
- assert result.session.model == model
+ assert result.session.model is not None
+ assert (result.session.model.id, result.session.model.provider_id) == ("claude", "anthropic")
assert result.session.cost == 0.5
assert fake.prompts == ["hello"]
diff --git a/tests/custom/test_live.py b/tests/custom/test_live.py
index 5ec7084..fffe03d 100644
--- a/tests/custom/test_live.py
+++ b/tests/custom/test_live.py
@@ -22,6 +22,7 @@
rpc_method,
session_id_of,
)
+from opencode.types.shared_params import ModelRef as ModelRefParam
from opencode.types import ModelRef, ServerConnectedEvent
pytestmark = [
@@ -163,7 +164,7 @@ def test_wait_until_ready(client: Opencode) -> None:
def test_run_with_unroutable_model(client: Opencode) -> None:
# A model of a provider that does not exist fails before any network call, so no model is called.
# Never prompt without such a model: the default would use OpenCode's hosted free model.
- model = ModelRef.model_validate({"id": "none", "providerID": "sdk-test-missing"})
+ model: ModelRefParam = {"id": "none", "provider_id": "sdk-test-missing"}
result = client.session.run("hello", model=model, timeout=30)
assert [message.type for message in result.messages] == ["user", "idle"]
assert result.outcome == "failed"
diff --git a/tests/custom/test_pagination.py b/tests/custom/test_pagination.py
index 85be822..a7156c6 100644
--- a/tests/custom/test_pagination.py
+++ b/tests/custom/test_pagination.py
@@ -35,11 +35,11 @@ def test_manual_page_control(client: Opencode, mock_api: respx.MockRouter) -> No
)
page = client.message.list("ses_1")
assert page.has_next_page()
- following = page.next_page()
+ following = page.get_next_page()
assert [message.id for message in following.data] == ["b"]
assert not following.has_next_page()
- with pytest.raises(StopIteration):
- following.next_page()
+ with pytest.raises(RuntimeError):
+ following.get_next_page()
@pytest.mark.anyio
@@ -54,4 +54,5 @@ async def test_async_paginator(async_client: AsyncOpencode, mock_api: respx.Mock
ids = [message.id async for message in async_client.message.list("ses_1")]
assert ids == ["a", "b"]
first = await async_client.message.list("ses_1")
- assert first.next_cursor == "c1"
+ assert first.cursor is not None
+ assert first.cursor.next == "c1"
diff --git a/tests/custom/test_rpc.py b/tests/custom/test_rpc.py
index ba1c335..d1714e8 100644
--- a/tests/custom/test_rpc.py
+++ b/tests/custom/test_rpc.py
@@ -76,7 +76,7 @@ def test_call_without_input_uses_declared_name(client: Opencode, mock_api: respx
def test_generated_call_still_available(client: Opencode, mock_api: respx.MockRouter) -> None:
mock_api.post("/api/rpc/counter/add").respond(json={"output": 1})
- assert client.rpc.call("counter", "add", input={"amount": 1}).output == 1
+ assert client.rpc.call("add", rpc_id="counter", input={"amount": 1}).output == 1
def test_rpc_error_maps_to_rpc_call_error_with_typed_data(client: Opencode, mock_api: respx.MockRouter) -> None:
From 4fabaf96a6361c85eaafe147f02141c5fe27bde9 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 17:08:24 +0530
Subject: [PATCH 13/41] chore: regenerate SDK
Ferrule-Generated-From: 75c5889180be6dc600188eddc063e1475e3e3b87
---
ferrule.lock | 76 ++--
skills/opencode/SKILL.md | 26 +-
src/opencode/_agent/SKILL.md | 26 +-
.../experimental/test_persistent_pty.py | 112 ++---
.../api_resources/integration/test_command.py | 116 +++---
.../api_resources/integration/test_connect.py | 52 +--
tests/api_resources/integration/test_oauth.py | 156 +++----
tests/api_resources/permission/test_saved.py | 28 +-
tests/api_resources/pty/test_connect.py | 16 +-
.../session/instructions/test_entry.py | 76 ++--
tests/api_resources/session/test_form.py | 96 ++---
tests/api_resources/session/test_inbox.py | 96 ++---
tests/api_resources/session/test_message.py | 32 +-
tests/api_resources/session/test_revert.py | 60 +--
tests/api_resources/test_agent.py | 16 +-
tests/api_resources/test_credential.py | 56 +--
tests/api_resources/test_integration.py | 16 +-
tests/api_resources/test_message.py | 16 +-
tests/api_resources/test_permission.py | 124 +++---
tests/api_resources/test_project.py | 20 +-
tests/api_resources/test_provider.py | 16 +-
tests/api_resources/test_pty.py | 48 +--
tests/api_resources/test_session.py | 384 +++++++++---------
tests/api_resources/test_shell.py | 48 +--
tests/api_resources/test_vcs.py | 16 +-
tests/api_resources/test_websearch.py | 4 +-
tests/api_resources/test_worktree.py | 72 ++--
tests/test_client.py | 26 +-
28 files changed, 903 insertions(+), 927 deletions(-)
diff --git a/ferrule.lock b/ferrule.lock
index fbed66f..d873278 100644
--- a/ferrule.lock
+++ b/ferrule.lock
@@ -16,46 +16,46 @@
"examples/agent/file_list.py": "f66fd853bcbfd869127d3914f7eb6072df538ec2f90d367b8013acddc09d2ecd",
"examples/agent/form_list.py": "2e90ff861855be774507386175d7fa22bc453a5895e36394a6c2e8eb5ea45470",
"examples/agent/generate_text.py": "54f6ccbfba4dca6c733ec337d4a9232077ffc9bbe8d8e01330dfb2ee6fc1ca1b",
- "examples/agent/integration_command_cancel.py": "a805b06e70c136a51a172d6496bd02e3ad50e00de0091cdb76169f8debc02217",
- "examples/agent/integration_connect_external.py": "8bdd84ac847d3ab6cea2af901c0214a4179d7a69467d7c3131fe10962ee23a1c",
+ "examples/agent/integration_command_cancel.py": "9e0122479be9cad2e4e775983dfd9c87dca1298d37fca65be5088696dc6d4610",
+ "examples/agent/integration_connect_external.py": "e92d01409e2a49c14733cdd3a053c7d1ad60a64646030bb3cfc99dee2f2b20c1",
"examples/agent/integration_list.py": "9832387109077a8db224357b03abcf8e94543b0c3bef314129a8cf15a6ce8c63",
- "examples/agent/integration_oauth_cancel.py": "2f4958831d813dd0445fc6a24b4b5aae953f7aec99e648c19d047a6672e8ff1d",
+ "examples/agent/integration_oauth_cancel.py": "3e93ddc3f4d288f48f9f2b95c64b2eed99ae321e3acc5f83d71dcbec164ea3c9",
"examples/agent/integration_wellknown_add.py": "c6d5bba29b854b6bc1464daa3a6487cd27bdc8a1cb6a1eda004ab7fea6428826",
"examples/agent/location_get.py": "db2a0c118ea0bd020947aa661a6fb62007e9536be3960bdd2ebc4791bd07101e",
"examples/agent/mcp_list.py": "ef35aafe845ec46a4beaaf4d6930a3708a1e0396297947cc538aeb5cfef994c8",
"examples/agent/mcp_resource_catalog.py": "384f7216b8bb1005bb7a4ae67cf4fca179a03a4b16caf6a5851a544b6853faa9",
"examples/agent/migration_v1_status.py": "ac85bedc4e6278e150a8c19a79bea6fd8fc8c33652ea1cb78b69824484805326",
"examples/agent/model_list.py": "f57d438c91a9cd4debe75f97db7c5150129cb09d1b60e8a82b40e00835f46cc0",
- "examples/agent/permission_create.py": "0d5922e0ff252b55379932593d59979ece3b496660117b4a10975961c748f518",
+ "examples/agent/permission_create.py": "1748c01bdc7c36ea517ee23ae5a575759a4e6260169efff892d91136dd63843f",
"examples/agent/permission_request_list.py": "8873887ce4a170c1dc504dd66770dceaf47de1e8d0854e1db695602c4d6331fd",
"examples/agent/permission_saved_list.py": "508030fd89b7d7c72d4778d47eb17275f040e406fb0c407cb1e0f6dc793cf31a",
"examples/agent/plugin_list.py": "2da03ca0e92c711ed8f356461e831f929c62cbbcc113dacce846d6c7cce8b052",
"examples/agent/project_list.py": "dc7f942e51170ee0ee4b49c817543135bbaf4f82422c5efdf08cbb60f6f34a68",
"examples/agent/provider_list.py": "de9b3661951a52004a03ff2a9053e856bebead8247326ceccdd5c051ad1ab333",
- "examples/agent/pty_connect_token.py": "6b4876060338285842f250bdcc9e79f9128a2ced0bf4d325797061222678cd29",
+ "examples/agent/pty_connect_token.py": "ce5f7f35ed9807a3b4a76468b0466f740305e632ae37b3ec21c9484b52ed25fa",
"examples/agent/pty_create.py": "b7b03ddaf5a224fd450d84719369dbe7fb67c1b2bf35acf11ac7e90776d36edb",
"examples/agent/reference_list.py": "ad72fab168edce0e86ad301b0fff59c16958c273cd7fc1c4fcce1fceb3cd2e3c",
"examples/agent/rpc_call.py": "c89e2ad5e860682c6da65adbd5b76ce1087fab4abf598fff4f26e03288c50bc4",
"examples/agent/server_info.py": "0070e224f86aab73b5812ab1574a0d95cbae0a24a6805de26993cf33dd621241",
"examples/agent/session_create.py": "f1796fc1c024f01457eda899063930ead30d288a52e678a813675a6d4b604ed6",
- "examples/agent/session_form_create.py": "505abd072ca25e35064e61ebe1f9081686e85849bab1acedd725896c253255b9",
- "examples/agent/session_inbox_update.py": "e8a179772028584345dd4a3f1d348b3fadb55f56ea599c23c32026da486f27e4",
- "examples/agent/session_instructions_entry_list.py": "bbd2e00d0816057795652f009e804a0b7f6ddc971af7b02d6f8560e3776723d0",
- "examples/agent/session_message_get.py": "09da8ac11f73723ab6ab9d1ea3b54ff37db332483db570c865c5c27264bd0e12",
- "examples/agent/session_revert_clear.py": "cdc8eab5957fbe4f44396cdd908e706e250f965f88d8251a48c008209300ff96",
+ "examples/agent/session_form_create.py": "daeac58a5cd0a5c898e546ee80bef63135fe2d440463dee65b0e9d2d955a9712",
+ "examples/agent/session_inbox_update.py": "0586f72cbe3ec53325c87d941c839546fb576b45551709adff1d799e6cb4baaf",
+ "examples/agent/session_instructions_entry_list.py": "850a49ed898caf7c3c358ba58e47716af0af46088e5e4876106cab95082b9476",
+ "examples/agent/session_message_get.py": "4aa849478ce5cf99c67d138e6586991bf0b5b5ff03c11bc51e78725e570e97c0",
+ "examples/agent/session_revert_clear.py": "6677aeed4b53ac0369b868a58bb6207c99afbd49bc1e550c3bd1844a3151b692",
"examples/agent/shell_list.py": "1752cd895257899fc387e09099349c6bb665e6ec02e9dd9e0499d678b1d3bd74",
"examples/agent/skill_list.py": "6a494c2a14749fb27d3cc902478d99686766f05b35ad19cba42259c5ac1c327d",
"examples/agent/vcs_base.py": "861ef19cdd183b27b1ef8ed3170ea41c416b58f8d75f02583c560158290b62b6",
"examples/agent/vcs_branch_list.py": "5d965ec1673776070a6ff0acfb41543a36da785c42df31b801b0554ff03c62b8",
"examples/agent/voice_session.py": "07ff5b3e962cd6eb27e0c852ca13a84f9af87f2628394f2100ada9e93e069899",
"examples/agent/websearch_providers.py": "30bb99ecdaf39b3c2112112a05c3b844aacfd24e298a4e6cdb40286d7954c3b2",
- "examples/agent/worktree_create.py": "58d6fe4b42f4ecee2fa8e379abdeaa8a222c2b986c8acd07e83c49e05ada9fc3",
+ "examples/agent/worktree_create.py": "c5222de9df346efdafc2e7b7c12449aa502a29e309731c54a9581406e0eaaf59",
"llms.txt": "c9d8fa2e8580039de36b140ca06f2cb5558ff3d2b42e16effbd3a46ed682eef4",
"scripts/utils/ruffen-docs.py": "da4c57fe200f40d65dc9cefdfb1fa65245c407daa595b6522f43c44f14b271af",
- "skills/opencode/SKILL.md": "5f52188520785747cc486ae9f2f094bbe00af72cdc06378c74632439ce3682f2",
+ "skills/opencode/SKILL.md": "4c7000cf263208e5c18829e52e5ad33f093c54beae487e3e3560f40220daf4a2",
"src/opencode/__init__.py": "9706cd16d41b78c7d20f1f32d04196bb7c11ffb5372dae6cf750eef7118db961",
"src/opencode/_agent/AGENTS.md": "7f135189e8ab203aea9abecf018f905e395767cd38c24c2d22c3c1230c04c5e5",
- "src/opencode/_agent/SKILL.md": "5f52188520785747cc486ae9f2f094bbe00af72cdc06378c74632439ce3682f2",
+ "src/opencode/_agent/SKILL.md": "4c7000cf263208e5c18829e52e5ad33f093c54beae487e3e3560f40220daf4a2",
"src/opencode/_agent/llms.txt": "c9d8fa2e8580039de36b140ca06f2cb5558ff3d2b42e16effbd3a46ed682eef4",
"src/opencode/_base_client.py": "32888441549574c13267ece8bfbc34394bfb41e6bef39633207fa8381805aae7",
"src/opencode/_client.py": "d14617ca1aea9d2266756cceba3e5c16c6b9b2b35fcf489a26128549cbec7684",
@@ -760,11 +760,11 @@
"tests/api_resources/debug/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/debug/test_location.py": "8dc2ed87566aac5a4c6412b90be8ab49ad2049c5f18cfd966efb8158750fe73d",
"tests/api_resources/experimental/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
- "tests/api_resources/experimental/test_persistent_pty.py": "347f9f91972bfa26476ee9582acfa4eb09d6e9305e6d0c5c67f39df96b915eb9",
+ "tests/api_resources/experimental/test_persistent_pty.py": "621920b7bc7c616bcc3c368de52fd204c1aac0b736e2271bb8449deb63b5023e",
"tests/api_resources/integration/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
- "tests/api_resources/integration/test_command.py": "7df2af237dfcb3bf7850a805fbe31004b50b55cbfab17d42c281c740d20e7986",
- "tests/api_resources/integration/test_connect.py": "d322a261f25500486c483b6da8566584319880939c8866bb36d6452bebf6299c",
- "tests/api_resources/integration/test_oauth.py": "f62af1022e7a0c73905c6173d4ffd0ec49ca9200f494dc432baee08f05ac2fc1",
+ "tests/api_resources/integration/test_command.py": "7f54f3b02479958b7e94085b1bf560f6297849289ba03582945f7a69b82e5c3b",
+ "tests/api_resources/integration/test_connect.py": "7797beeea54a6dee3da8b0affbbdb4e7cc72a3930537874efdaa864c96116309",
+ "tests/api_resources/integration/test_oauth.py": "ac268f0f6125c126e03f5d16f762f2a4d699227dbac2126b6356b53734f7a8d3",
"tests/api_resources/integration/test_wellknown.py": "6f875993fb7bb0a33acdde7a84b66b06e27d2ba9442b06009932f87de17daa67",
"tests/api_resources/mcp/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/mcp/test_resource.py": "9da53a0f18921b820d1df920ccc86deac6d961766d6a3bfa513ea0bcfe71b1ff",
@@ -772,50 +772,50 @@
"tests/api_resources/migration/test_v1.py": "53cf87accc6a464ab2828563d4f3cc68aab2969b317631d7aec8bbe92a07d21e",
"tests/api_resources/permission/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/permission/test_request.py": "7777ccc174ac2b7fbf310db96b4bca9340f89e198e110b0a4dcdf1a90f61c180",
- "tests/api_resources/permission/test_saved.py": "7760919e341b85cf388b6e5062696f102cbf6cf986723bd8e66d74b0748ce5ea",
+ "tests/api_resources/permission/test_saved.py": "2d2e6bc35f36ad3b129ebf5e8bf85a97efdfd8ac20d7078b2682987571910f76",
"tests/api_resources/pty/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
- "tests/api_resources/pty/test_connect.py": "4bd50ef37c3147d86a2415671c794495911fe43b10f690566db3e9b26724d56b",
+ "tests/api_resources/pty/test_connect.py": "fee2827917a519428c3103ec3b6fd6785b7f83f15d190d85ef9e230ee0f0295e",
"tests/api_resources/session/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/session/instructions/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
- "tests/api_resources/session/instructions/test_entry.py": "685b557c00560c99590879a310a8d676c9ea3d5e43397784808605241bfa8555",
- "tests/api_resources/session/test_form.py": "b6a70697bf9991b02b17cfe8048a637234dad94c8a80e9cc0e441f33d88f5e43",
- "tests/api_resources/session/test_inbox.py": "b36d22134a8107b5a5933b80136f00108c278643a433e542ce2cacb686d0821c",
- "tests/api_resources/session/test_message.py": "098ca06a0f108049c7ff234c155576095601334297ca0ccc1fcee735d7193b82",
- "tests/api_resources/session/test_revert.py": "c4d12c0c1d4be2183501ebf7e3e67bf8bc9ec5af0e0a6e8cdd9a087d57cb890e",
- "tests/api_resources/test_agent.py": "35ab8d142fda8bf9e27baeb229bbb91531da350985436a64c0b50bade38cde10",
+ "tests/api_resources/session/instructions/test_entry.py": "46b598f854ae1c7a63239751e12e8d23d65e5012f4a053129d8976a9b053a23b",
+ "tests/api_resources/session/test_form.py": "17ee2b31db56e53b905629895a95820bb73d67c415ab15499aa24259ac3985aa",
+ "tests/api_resources/session/test_inbox.py": "c89953a98bca37a57cb76a668a5ef23ed8cf6fdb94388146ceb9316e6e448b45",
+ "tests/api_resources/session/test_message.py": "9f02aa1f294d39aac79ed24daa2b65e3b78666830b281014ddaa76f4e33c138f",
+ "tests/api_resources/session/test_revert.py": "e1e1df2eb510bfa0d9d57299d61eef9bd4f2c5eaa4e2e99937fa22ada21f78a9",
+ "tests/api_resources/test_agent.py": "7f6c7a832a84884620e1da85e4b2bb8529ee711ff31cec23563f4fa9d1d51a2a",
"tests/api_resources/test_command.py": "8be3e96e7ff2ad6750fc1085ce6d422ca8e1cda3b2443dca2de0953c8f9edee5",
"tests/api_resources/test_config.py": "3b646f5206a3143c00ad183ba3a1f7cbc0a588058e8ab9751d316609cd5da218",
- "tests/api_resources/test_credential.py": "bd29b2ed147895b6f26fee737ae22ce060534400af7203e869eed686b9a16322",
+ "tests/api_resources/test_credential.py": "49d2dcf793a8c515774142199582929e991de383170e12a1a2b4b49b4e77f451",
"tests/api_resources/test_event.py": "dea5556b0932db3ed178262dd925e3aeeda69783ebf313de0b6c20dd2f600205",
"tests/api_resources/test_file.py": "6b62a1a08c9a76d179e5829e530986df09586373a747986667edda47f4977e33",
"tests/api_resources/test_form.py": "35fdd2d079242efe0a6c26c06198910ed89a2cc1421e5f74509dd202fd830ae5",
"tests/api_resources/test_generate.py": "7c4b732035560457d0871957f2f686298eff06f23244f9ec3be4c69296b3576b",
- "tests/api_resources/test_integration.py": "d0c6e645d30405dea9bf91f7880d1c1e60d28ede6d25e07de8438cadca501911",
+ "tests/api_resources/test_integration.py": "e2ae3ea7fcff53120d0ee3c990f6bc7517e281cd03909e3f74c33cf535269bcc",
"tests/api_resources/test_location.py": "40f6986acf63ed7e0e279c9df41e970405ac2474f95928f28112921d8d16f13d",
"tests/api_resources/test_mcp.py": "e99fa31e92a226799a058e9408a9b9bf3ac9290169a8a40019024aae73fe188b",
- "tests/api_resources/test_message.py": "b7b4654daa42cd2f70d55fdb0f8846d9003eea21327fd6d10122d2cdf4ffe775",
+ "tests/api_resources/test_message.py": "797e28f5b7d53858478f346ccb89051c48fe6134952a409461f897ae4adaf74e",
"tests/api_resources/test_model.py": "afd0aa11174a17f25cb7a060597d913202359074c20676ee8b0dabed853548bd",
- "tests/api_resources/test_permission.py": "d9e053899f074d8b2bb675916ffdc1ebde450fb84076622194d69be2feaae336",
+ "tests/api_resources/test_permission.py": "54c03d5007e1be9e9f05fcfac1fdcf1373ad7506ba11a926eb66a78f6ea71d1a",
"tests/api_resources/test_plugin.py": "2685fa322627066f274e068959165468524db459aa47684976f57e13f35bb05b",
- "tests/api_resources/test_project.py": "c9765bd34fb9ebaad4ed81bcb96f8375377674ab5a347ac4e662f8a8aa48945f",
- "tests/api_resources/test_provider.py": "c44d0ea1ce6fc310e919cd2787d673b5af8b1e841f69fa5a3b96534198a58d0d",
- "tests/api_resources/test_pty.py": "2afd6351a3638aeabb518b97a4d697af0b7f027787ab24d28c5a25d926bd3a6f",
+ "tests/api_resources/test_project.py": "de21eebfa92b08ced8035a43ee783fb8a9805cb30b4d80b703c37449070bfae9",
+ "tests/api_resources/test_provider.py": "e83de14c66e0265613e5c0afd79cae2935a501699ee03d2aa184c21c9117b6b5",
+ "tests/api_resources/test_pty.py": "cfb5b62e31ccfea29e643ec9eb6b7bbc597aa6a3f57c2662e6fd8aac50a57c25",
"tests/api_resources/test_reference.py": "82661f6cecc6f0fe44d63fd1180817b27869845578f5d71d039916ee1679b8ec",
"tests/api_resources/test_rpc.py": "1dabbd8dbdab2902c05721ea5c344bd57f4c4e5c85af15c9ff60dd650baf4907",
"tests/api_resources/test_server.py": "bf49f7693308b7ede9fd1cf2e4e555dce88ec560c6e908393020e3ebc7fac9db",
- "tests/api_resources/test_session.py": "192e3371b4044440886fa9e4fe0712894dda1684b27f2c4da8ab26ef9074afb1",
- "tests/api_resources/test_shell.py": "a7351fb690081ad106b0b210a9ae1c8fe0c1a65a2e801487906f0db71b357062",
+ "tests/api_resources/test_session.py": "77932efdee0b5f69066a17aed4816b14f87849bc1465d586b9169316d1cfaf86",
+ "tests/api_resources/test_shell.py": "544c8e14854989a874fc1e6e5ca9067ccae4bfd44f81ea8a845fa101176181d4",
"tests/api_resources/test_skill.py": "cf22c66273943ec809067cfac8d6b9d14f71e93f862b1be615ae2c51933c34cf",
- "tests/api_resources/test_vcs.py": "353ea1976920276f2360acf7e720b3c4cbec2f081c9161fea09c3765ca71b25f",
+ "tests/api_resources/test_vcs.py": "502249bc039765b74d0860cc151f48f1cc5f2ad4456d123b2c950253411e6bb1",
"tests/api_resources/test_voice.py": "8db95fb7a02962a82459891692c404f897b10e0670e0da6d6e27f451a0e4480c",
- "tests/api_resources/test_websearch.py": "21e96ecd0b6cce3c1c7b376a54e782241d6ac78b6a6efc9e8dc9ae942ba79823",
- "tests/api_resources/test_worktree.py": "55ea8bfa15ea226faed5a35fd4a4b8ad0ab87a94249ef09e89a97a5da848400b",
+ "tests/api_resources/test_websearch.py": "2bb8b9c82ab1a8f74ec1b057d751e90be6816d82d131876226c651b3a3644f4c",
+ "tests/api_resources/test_worktree.py": "3a7a0f96d07471e8176011b1bd06bb7884585b04b203a3f5a42336c5ecb45b26",
"tests/api_resources/vcs/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/vcs/test_branch.py": "aaf9fc847c3420217ffb3e61bb88672f8e3572851343b28bd5f78481f68d51ef",
"tests/conftest.py": "d78027f663a4bd3645aed8ae0fb54e29eabdd3f823c5b4e19914b23d00b695d7",
"tests/sample_file.txt": "d9014c4624844aa5bac314773d6b689ad467fa4e1d1a50a1b8a99d5a95f72ff5",
"tests/test_agent_recipes.py": "441160bca6398e19b21d9df20c4252110c6ea956d7e8bed7d423c66875584bed",
- "tests/test_client.py": "71a84d341fca963f85796323845d37102324befeb26299f99e8b69f0d195bdb3",
+ "tests/test_client.py": "d1cf69cdddf1d6b9a6e3098e81c23558d7a715d69c7eb47fb7a9892569582caf",
"tests/test_extract_files.py": "9607c4071ebd885825b797690e8acddd4a8c76b3e2b7a1d8c3e2354b2134665d",
"tests/test_files.py": "3ee0304de14f5f9c6da3a8bd45005329072773bcb048b4aa80bb4b58715c3f2d",
"tests/test_models.py": "378192fabdd9db34e32b7974ee7f90bcc36644bf7f8f8709cdb37c53b6aa37f9",
diff --git a/skills/opencode/SKILL.md b/skills/opencode/SKILL.md
index ef5384d..5d2a604 100644
--- a/skills/opencode/SKILL.md
+++ b/skills/opencode/SKILL.md
@@ -173,7 +173,7 @@ from opencode import Opencode
client = Opencode()
-connect = client.integration.connect.external(integration_id={}, method_id={})
+connect = client.integration.connect.external(integration_id="integrationID", method_id="methodID")
print(connect)
```
@@ -184,7 +184,7 @@ from opencode import Opencode
client = Opencode()
-oauth = client.integration.oauth.cancel(integration_id={}, attempt_id={})
+oauth = client.integration.oauth.cancel(integration_id="integrationID", attempt_id="attemptID")
print(oauth)
```
@@ -195,7 +195,7 @@ from opencode import Opencode
client = Opencode()
-command = client.integration.command.cancel(integration_id={}, attempt_id={})
+command = client.integration.command.cancel(integration_id="integrationID", attempt_id="attemptID")
print(command)
```
@@ -239,7 +239,7 @@ from opencode import Opencode
client = Opencode()
-message = client.message.list(session_id={})
+message = client.message.list(session_id="sessionID")
print(message)
```
@@ -272,7 +272,7 @@ from opencode import Opencode
client = Opencode()
-permission = client.permission.create(session_id={}, action="action", resources=["string"])
+permission = client.permission.create(session_id="sessionID", action="action", resources=["string"])
print(permission)
```
@@ -349,7 +349,7 @@ from opencode import Opencode
client = Opencode()
-connect = client.pty.connect.token(pty_id={})
+connect = client.pty.connect.token(pty_id="ptyID")
print(connect)
```
@@ -404,7 +404,7 @@ from opencode import Opencode
client = Opencode()
-revert = client.session.revert.clear(session_id={})
+revert = client.session.revert.clear(session_id="sessionID")
print(revert)
```
@@ -415,7 +415,7 @@ from opencode import Opencode
client = Opencode()
-inbox = client.session.inbox.update(session_id={}, inbox_id={}, delivery={})
+inbox = client.session.inbox.update(session_id="sessionID", inbox_id="inboxID", delivery="steer")
print(inbox)
```
@@ -426,7 +426,7 @@ from opencode import Opencode
client = Opencode()
-entry = client.session.instructions.entry.list(session_id={})
+entry = client.session.instructions.entry.list(session_id="sessionID")
print(entry)
```
@@ -437,7 +437,7 @@ from opencode import Opencode
client = Opencode()
-message = client.session.message.get(session_id={}, message_id={})
+message = client.session.message.get(session_id="sessionID", message_id="messageID")
print(message)
```
@@ -448,7 +448,7 @@ from opencode import Opencode
client = Opencode()
-form = client.session.form.create(session_id="sessionID", fields={}, title="title")
+form = client.session.form.create(session_id="sessionID", fields=[{}], title="title")
print(form)
```
@@ -525,7 +525,7 @@ from opencode import Opencode
client = Opencode()
-worktree = client.worktree.create(project_id={})
+worktree = client.worktree.create(project_id="projectID")
print(worktree)
```
@@ -537,7 +537,7 @@ from opencode import Opencode
client = Opencode()
# Iterating the page fetches the next pages as needed.
-for item in client.message.list(session_id={}):
+for item in client.message.list(session_id="sessionID"):
print(item)
```
diff --git a/src/opencode/_agent/SKILL.md b/src/opencode/_agent/SKILL.md
index ef5384d..5d2a604 100644
--- a/src/opencode/_agent/SKILL.md
+++ b/src/opencode/_agent/SKILL.md
@@ -173,7 +173,7 @@ from opencode import Opencode
client = Opencode()
-connect = client.integration.connect.external(integration_id={}, method_id={})
+connect = client.integration.connect.external(integration_id="integrationID", method_id="methodID")
print(connect)
```
@@ -184,7 +184,7 @@ from opencode import Opencode
client = Opencode()
-oauth = client.integration.oauth.cancel(integration_id={}, attempt_id={})
+oauth = client.integration.oauth.cancel(integration_id="integrationID", attempt_id="attemptID")
print(oauth)
```
@@ -195,7 +195,7 @@ from opencode import Opencode
client = Opencode()
-command = client.integration.command.cancel(integration_id={}, attempt_id={})
+command = client.integration.command.cancel(integration_id="integrationID", attempt_id="attemptID")
print(command)
```
@@ -239,7 +239,7 @@ from opencode import Opencode
client = Opencode()
-message = client.message.list(session_id={})
+message = client.message.list(session_id="sessionID")
print(message)
```
@@ -272,7 +272,7 @@ from opencode import Opencode
client = Opencode()
-permission = client.permission.create(session_id={}, action="action", resources=["string"])
+permission = client.permission.create(session_id="sessionID", action="action", resources=["string"])
print(permission)
```
@@ -349,7 +349,7 @@ from opencode import Opencode
client = Opencode()
-connect = client.pty.connect.token(pty_id={})
+connect = client.pty.connect.token(pty_id="ptyID")
print(connect)
```
@@ -404,7 +404,7 @@ from opencode import Opencode
client = Opencode()
-revert = client.session.revert.clear(session_id={})
+revert = client.session.revert.clear(session_id="sessionID")
print(revert)
```
@@ -415,7 +415,7 @@ from opencode import Opencode
client = Opencode()
-inbox = client.session.inbox.update(session_id={}, inbox_id={}, delivery={})
+inbox = client.session.inbox.update(session_id="sessionID", inbox_id="inboxID", delivery="steer")
print(inbox)
```
@@ -426,7 +426,7 @@ from opencode import Opencode
client = Opencode()
-entry = client.session.instructions.entry.list(session_id={})
+entry = client.session.instructions.entry.list(session_id="sessionID")
print(entry)
```
@@ -437,7 +437,7 @@ from opencode import Opencode
client = Opencode()
-message = client.session.message.get(session_id={}, message_id={})
+message = client.session.message.get(session_id="sessionID", message_id="messageID")
print(message)
```
@@ -448,7 +448,7 @@ from opencode import Opencode
client = Opencode()
-form = client.session.form.create(session_id="sessionID", fields={}, title="title")
+form = client.session.form.create(session_id="sessionID", fields=[{}], title="title")
print(form)
```
@@ -525,7 +525,7 @@ from opencode import Opencode
client = Opencode()
-worktree = client.worktree.create(project_id={})
+worktree = client.worktree.create(project_id="projectID")
print(worktree)
```
@@ -537,7 +537,7 @@ from opencode import Opencode
client = Opencode()
# Iterating the page fetches the next pages as needed.
-for item in client.message.list(session_id={}):
+for item in client.message.list(session_id="sessionID"):
print(item)
```
diff --git a/tests/api_resources/experimental/test_persistent_pty.py b/tests/api_resources/experimental/test_persistent_pty.py
index b7759e5..aa49317 100644
--- a/tests/api_resources/experimental/test_persistent_pty.py
+++ b/tests/api_resources/experimental/test_persistent_pty.py
@@ -29,7 +29,7 @@ class TestPersistentPty:
@parametrize
def test_method_create(self, client: Opencode) -> None:
persistent_pty = client.experimental.persistent_pty.create(
- session_id={},
+ session_id="sessionID",
args=["string"],
env={"foo": "string"},
title="title",
@@ -39,7 +39,7 @@ def test_method_create(self, client: Opencode) -> None:
@parametrize
def test_method_create_with_all_params(self, client: Opencode) -> None:
persistent_pty = client.experimental.persistent_pty.create(
- session_id={},
+ session_id="sessionID",
args=["string"],
env={"foo": "string"},
title="title",
@@ -55,7 +55,7 @@ def test_method_create_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_create(self, client: Opencode) -> None:
response = client.experimental.persistent_pty.with_raw_response.create(
- session_id={},
+ session_id="sessionID",
args=["string"],
env={"foo": "string"},
title="title",
@@ -69,7 +69,7 @@ def test_raw_response_create(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_create(self, client: Opencode) -> None:
with client.experimental.persistent_pty.with_streaming_response.create(
- session_id={},
+ session_id="sessionID",
args=["string"],
env={"foo": "string"},
title="title",
@@ -95,7 +95,7 @@ def test_path_params_create(self, client: Opencode) -> None:
@parametrize
def test_method_update(self, client: Opencode) -> None:
persistent_pty = client.experimental.persistent_pty.update(
- pty_id={},
+ pty_id="ptyID",
size={
"cols": 0,
"rows": 0,
@@ -106,7 +106,7 @@ def test_method_update(self, client: Opencode) -> None:
@parametrize
def test_method_update_with_all_params(self, client: Opencode) -> None:
persistent_pty = client.experimental.persistent_pty.update(
- pty_id={},
+ pty_id="ptyID",
size={
"cols": 0,
"rows": 0,
@@ -118,7 +118,7 @@ def test_method_update_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_update(self, client: Opencode) -> None:
response = client.experimental.persistent_pty.with_raw_response.update(
- pty_id={},
+ pty_id="ptyID",
size={
"cols": 0,
"rows": 0,
@@ -133,7 +133,7 @@ def test_raw_response_update(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_update(self, client: Opencode) -> None:
with client.experimental.persistent_pty.with_streaming_response.update(
- pty_id={},
+ pty_id="ptyID",
size={
"cols": 0,
"rows": 0,
@@ -161,14 +161,14 @@ def test_path_params_update(self, client: Opencode) -> None:
@parametrize
def test_method_list(self, client: Opencode) -> None:
persistent_pty = client.experimental.persistent_pty.list(
- {},
+ "sessionID",
)
assert_matches_type(PersistentPtyListResponse, persistent_pty, path=["response"])
@parametrize
def test_raw_response_list(self, client: Opencode) -> None:
response = client.experimental.persistent_pty.with_raw_response.list(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -179,7 +179,7 @@ def test_raw_response_list(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_list(self, client: Opencode) -> None:
with client.experimental.persistent_pty.with_streaming_response.list(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -199,14 +199,14 @@ def test_path_params_list(self, client: Opencode) -> None:
@parametrize
def test_method_connect_token(self, client: Opencode) -> None:
persistent_pty = client.experimental.persistent_pty.connect_token(
- pty_id={},
+ pty_id="ptyID",
)
assert_matches_type(PtyTicketConnectToken, persistent_pty, path=["response"])
@parametrize
def test_method_connect_token_with_all_params(self, client: Opencode) -> None:
persistent_pty = client.experimental.persistent_pty.connect_token(
- pty_id={},
+ pty_id="ptyID",
x_opencode_ticket="x-opencode-ticket",
)
assert_matches_type(PtyTicketConnectToken, persistent_pty, path=["response"])
@@ -214,7 +214,7 @@ def test_method_connect_token_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_connect_token(self, client: Opencode) -> None:
response = client.experimental.persistent_pty.with_raw_response.connect_token(
- pty_id={},
+ pty_id="ptyID",
)
assert response.is_closed is True
@@ -225,7 +225,7 @@ def test_raw_response_connect_token(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_connect_token(self, client: Opencode) -> None:
with client.experimental.persistent_pty.with_streaming_response.connect_token(
- pty_id={},
+ pty_id="ptyID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -245,14 +245,14 @@ def test_path_params_connect_token(self, client: Opencode) -> None:
@parametrize
def test_method_get(self, client: Opencode) -> None:
persistent_pty = client.experimental.persistent_pty.get(
- {},
+ "ptyID",
)
assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
@parametrize
def test_raw_response_get(self, client: Opencode) -> None:
response = client.experimental.persistent_pty.with_raw_response.get(
- {},
+ "ptyID",
)
assert response.is_closed is True
@@ -263,7 +263,7 @@ def test_raw_response_get(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_get(self, client: Opencode) -> None:
with client.experimental.persistent_pty.with_streaming_response.get(
- {},
+ "ptyID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -308,14 +308,14 @@ def test_streaming_response_handoff(self, client: Opencode) -> None:
@parametrize
def test_method_read(self, client: Opencode) -> None:
persistent_pty = client.experimental.persistent_pty.read(
- session_id={},
+ session_id="sessionID",
)
assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
@parametrize
def test_method_read_with_all_params(self, client: Opencode) -> None:
persistent_pty = client.experimental.persistent_pty.read(
- session_id={},
+ session_id="sessionID",
lines={},
)
assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
@@ -323,7 +323,7 @@ def test_method_read_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_read(self, client: Opencode) -> None:
response = client.experimental.persistent_pty.with_raw_response.read(
- session_id={},
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -334,7 +334,7 @@ def test_raw_response_read(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_read(self, client: Opencode) -> None:
with client.experimental.persistent_pty.with_streaming_response.read(
- session_id={},
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -354,14 +354,14 @@ def test_path_params_read(self, client: Opencode) -> None:
@parametrize
def test_method_remove(self, client: Opencode) -> None:
persistent_pty = client.experimental.persistent_pty.remove(
- {},
+ "ptyID",
)
assert persistent_pty is None
@parametrize
def test_raw_response_remove(self, client: Opencode) -> None:
response = client.experimental.persistent_pty.with_raw_response.remove(
- {},
+ "ptyID",
)
assert response.is_closed is True
@@ -372,7 +372,7 @@ def test_raw_response_remove(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_remove(self, client: Opencode) -> None:
with client.experimental.persistent_pty.with_streaming_response.remove(
- {},
+ "ptyID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -417,14 +417,14 @@ def test_streaming_response_shutdown(self, client: Opencode) -> None:
@parametrize
def test_method_snapshot(self, client: Opencode) -> None:
persistent_pty = client.experimental.persistent_pty.snapshot(
- {},
+ "ptyID",
)
assert_matches_type(PersistentPtySnapshot, persistent_pty, path=["response"])
@parametrize
def test_raw_response_snapshot(self, client: Opencode) -> None:
response = client.experimental.persistent_pty.with_raw_response.snapshot(
- {},
+ "ptyID",
)
assert response.is_closed is True
@@ -435,7 +435,7 @@ def test_raw_response_snapshot(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_snapshot(self, client: Opencode) -> None:
with client.experimental.persistent_pty.with_streaming_response.snapshot(
- {},
+ "ptyID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -461,7 +461,7 @@ class TestAsyncPersistentPty:
@parametrize
async def test_method_create(self, async_client: AsyncOpencode) -> None:
persistent_pty = await async_client.experimental.persistent_pty.create(
- session_id={},
+ session_id="sessionID",
args=["string"],
env={"foo": "string"},
title="title",
@@ -471,7 +471,7 @@ async def test_method_create(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
persistent_pty = await async_client.experimental.persistent_pty.create(
- session_id={},
+ session_id="sessionID",
args=["string"],
env={"foo": "string"},
title="title",
@@ -487,7 +487,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
response = await async_client.experimental.persistent_pty.with_raw_response.create(
- session_id={},
+ session_id="sessionID",
args=["string"],
env={"foo": "string"},
title="title",
@@ -501,7 +501,7 @@ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
async with async_client.experimental.persistent_pty.with_streaming_response.create(
- session_id={},
+ session_id="sessionID",
args=["string"],
env={"foo": "string"},
title="title",
@@ -527,7 +527,7 @@ async def test_path_params_create(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_update(self, async_client: AsyncOpencode) -> None:
persistent_pty = await async_client.experimental.persistent_pty.update(
- pty_id={},
+ pty_id="ptyID",
size={
"cols": 0,
"rows": 0,
@@ -538,7 +538,7 @@ async def test_method_update(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_update_with_all_params(self, async_client: AsyncOpencode) -> None:
persistent_pty = await async_client.experimental.persistent_pty.update(
- pty_id={},
+ pty_id="ptyID",
size={
"cols": 0,
"rows": 0,
@@ -550,7 +550,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
response = await async_client.experimental.persistent_pty.with_raw_response.update(
- pty_id={},
+ pty_id="ptyID",
size={
"cols": 0,
"rows": 0,
@@ -565,7 +565,7 @@ async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_update(self, async_client: AsyncOpencode) -> None:
async with async_client.experimental.persistent_pty.with_streaming_response.update(
- pty_id={},
+ pty_id="ptyID",
size={
"cols": 0,
"rows": 0,
@@ -593,14 +593,14 @@ async def test_path_params_update(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_list(self, async_client: AsyncOpencode) -> None:
persistent_pty = await async_client.experimental.persistent_pty.list(
- {},
+ "sessionID",
)
assert_matches_type(PersistentPtyListResponse, persistent_pty, path=["response"])
@parametrize
async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
response = await async_client.experimental.persistent_pty.with_raw_response.list(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -611,7 +611,7 @@ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
async with async_client.experimental.persistent_pty.with_streaming_response.list(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -631,14 +631,14 @@ async def test_path_params_list(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_connect_token(self, async_client: AsyncOpencode) -> None:
persistent_pty = await async_client.experimental.persistent_pty.connect_token(
- pty_id={},
+ pty_id="ptyID",
)
assert_matches_type(PtyTicketConnectToken, persistent_pty, path=["response"])
@parametrize
async def test_method_connect_token_with_all_params(self, async_client: AsyncOpencode) -> None:
persistent_pty = await async_client.experimental.persistent_pty.connect_token(
- pty_id={},
+ pty_id="ptyID",
x_opencode_ticket="x-opencode-ticket",
)
assert_matches_type(PtyTicketConnectToken, persistent_pty, path=["response"])
@@ -646,7 +646,7 @@ async def test_method_connect_token_with_all_params(self, async_client: AsyncOpe
@parametrize
async def test_raw_response_connect_token(self, async_client: AsyncOpencode) -> None:
response = await async_client.experimental.persistent_pty.with_raw_response.connect_token(
- pty_id={},
+ pty_id="ptyID",
)
assert response.is_closed is True
@@ -657,7 +657,7 @@ async def test_raw_response_connect_token(self, async_client: AsyncOpencode) ->
@parametrize
async def test_streaming_response_connect_token(self, async_client: AsyncOpencode) -> None:
async with async_client.experimental.persistent_pty.with_streaming_response.connect_token(
- pty_id={},
+ pty_id="ptyID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -677,14 +677,14 @@ async def test_path_params_connect_token(self, async_client: AsyncOpencode) -> N
@parametrize
async def test_method_get(self, async_client: AsyncOpencode) -> None:
persistent_pty = await async_client.experimental.persistent_pty.get(
- {},
+ "ptyID",
)
assert_matches_type(PersistentPtyInfo, persistent_pty, path=["response"])
@parametrize
async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
response = await async_client.experimental.persistent_pty.with_raw_response.get(
- {},
+ "ptyID",
)
assert response.is_closed is True
@@ -695,7 +695,7 @@ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
async with async_client.experimental.persistent_pty.with_streaming_response.get(
- {},
+ "ptyID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -740,14 +740,14 @@ async def test_streaming_response_handoff(self, async_client: AsyncOpencode) ->
@parametrize
async def test_method_read(self, async_client: AsyncOpencode) -> None:
persistent_pty = await async_client.experimental.persistent_pty.read(
- session_id={},
+ session_id="sessionID",
)
assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
@parametrize
async def test_method_read_with_all_params(self, async_client: AsyncOpencode) -> None:
persistent_pty = await async_client.experimental.persistent_pty.read(
- session_id={},
+ session_id="sessionID",
lines={},
)
assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
@@ -755,7 +755,7 @@ async def test_method_read_with_all_params(self, async_client: AsyncOpencode) ->
@parametrize
async def test_raw_response_read(self, async_client: AsyncOpencode) -> None:
response = await async_client.experimental.persistent_pty.with_raw_response.read(
- session_id={},
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -766,7 +766,7 @@ async def test_raw_response_read(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_read(self, async_client: AsyncOpencode) -> None:
async with async_client.experimental.persistent_pty.with_streaming_response.read(
- session_id={},
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -786,14 +786,14 @@ async def test_path_params_read(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_remove(self, async_client: AsyncOpencode) -> None:
persistent_pty = await async_client.experimental.persistent_pty.remove(
- {},
+ "ptyID",
)
assert persistent_pty is None
@parametrize
async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
response = await async_client.experimental.persistent_pty.with_raw_response.remove(
- {},
+ "ptyID",
)
assert response.is_closed is True
@@ -804,7 +804,7 @@ async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_remove(self, async_client: AsyncOpencode) -> None:
async with async_client.experimental.persistent_pty.with_streaming_response.remove(
- {},
+ "ptyID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -849,14 +849,14 @@ async def test_streaming_response_shutdown(self, async_client: AsyncOpencode) ->
@parametrize
async def test_method_snapshot(self, async_client: AsyncOpencode) -> None:
persistent_pty = await async_client.experimental.persistent_pty.snapshot(
- {},
+ "ptyID",
)
assert_matches_type(PersistentPtySnapshot, persistent_pty, path=["response"])
@parametrize
async def test_raw_response_snapshot(self, async_client: AsyncOpencode) -> None:
response = await async_client.experimental.persistent_pty.with_raw_response.snapshot(
- {},
+ "ptyID",
)
assert response.is_closed is True
@@ -867,7 +867,7 @@ async def test_raw_response_snapshot(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_snapshot(self, async_client: AsyncOpencode) -> None:
async with async_client.experimental.persistent_pty.with_streaming_response.snapshot(
- {},
+ "ptyID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/integration/test_command.py b/tests/api_resources/integration/test_command.py
index 0cc83d0..bbb315c 100644
--- a/tests/api_resources/integration/test_command.py
+++ b/tests/api_resources/integration/test_command.py
@@ -20,16 +20,16 @@ class TestCommand:
@parametrize
def test_method_cancel(self, client: Opencode) -> None:
command = client.integration.command.cancel(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert command is None
@parametrize
def test_method_cancel_with_all_params(self, client: Opencode) -> None:
command = client.integration.command.cancel(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
location={
"directory": "string",
},
@@ -39,8 +39,8 @@ def test_method_cancel_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_cancel(self, client: Opencode) -> None:
response = client.integration.command.with_raw_response.cancel(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert response.is_closed is True
@@ -51,8 +51,8 @@ def test_raw_response_cancel(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_cancel(self, client: Opencode) -> None:
with client.integration.command.with_streaming_response.cancel(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -66,29 +66,29 @@ def test_streaming_response_cancel(self, client: Opencode) -> None:
def test_path_params_cancel(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
client.integration.command.with_raw_response.cancel(
- attempt_id={},
+ attempt_id="attemptID",
integration_id="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
client.integration.command.with_raw_response.cancel(
attempt_id="",
- integration_id={},
+ integration_id="integrationID",
)
@parametrize
def test_method_connect(self, client: Opencode) -> None:
command = client.integration.command.connect(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
)
assert_matches_type(CommandConnectResponse, command, path=["response"])
@parametrize
def test_method_connect_with_all_params(self, client: Opencode) -> None:
command = client.integration.command.connect(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
label="label",
location={
"directory": "string",
@@ -99,8 +99,8 @@ def test_method_connect_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_connect(self, client: Opencode) -> None:
response = client.integration.command.with_raw_response.connect(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
)
assert response.is_closed is True
@@ -111,8 +111,8 @@ def test_raw_response_connect(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_connect(self, client: Opencode) -> None:
with client.integration.command.with_streaming_response.connect(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -127,22 +127,22 @@ def test_path_params_connect(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
client.integration.command.with_raw_response.connect(
integration_id="",
- method_id={},
+ method_id="methodID",
)
@parametrize
def test_method_status(self, client: Opencode) -> None:
command = client.integration.command.status(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert_matches_type(CommandStatusResponse, command, path=["response"])
@parametrize
def test_method_status_with_all_params(self, client: Opencode) -> None:
command = client.integration.command.status(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
location={
"directory": "string",
},
@@ -152,8 +152,8 @@ def test_method_status_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_status(self, client: Opencode) -> None:
response = client.integration.command.with_raw_response.status(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert response.is_closed is True
@@ -164,8 +164,8 @@ def test_raw_response_status(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_status(self, client: Opencode) -> None:
with client.integration.command.with_streaming_response.status(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -179,14 +179,14 @@ def test_streaming_response_status(self, client: Opencode) -> None:
def test_path_params_status(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
client.integration.command.with_raw_response.status(
- attempt_id={},
+ attempt_id="attemptID",
integration_id="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
client.integration.command.with_raw_response.status(
attempt_id="",
- integration_id={},
+ integration_id="integrationID",
)
@@ -198,16 +198,16 @@ class TestAsyncCommand:
@parametrize
async def test_method_cancel(self, async_client: AsyncOpencode) -> None:
command = await async_client.integration.command.cancel(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert command is None
@parametrize
async def test_method_cancel_with_all_params(self, async_client: AsyncOpencode) -> None:
command = await async_client.integration.command.cancel(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
location={
"directory": "string",
},
@@ -217,8 +217,8 @@ async def test_method_cancel_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_cancel(self, async_client: AsyncOpencode) -> None:
response = await async_client.integration.command.with_raw_response.cancel(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert response.is_closed is True
@@ -229,8 +229,8 @@ async def test_raw_response_cancel(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_cancel(self, async_client: AsyncOpencode) -> None:
async with async_client.integration.command.with_streaming_response.cancel(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -244,29 +244,29 @@ async def test_streaming_response_cancel(self, async_client: AsyncOpencode) -> N
async def test_path_params_cancel(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
await async_client.integration.command.with_raw_response.cancel(
- attempt_id={},
+ attempt_id="attemptID",
integration_id="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
await async_client.integration.command.with_raw_response.cancel(
attempt_id="",
- integration_id={},
+ integration_id="integrationID",
)
@parametrize
async def test_method_connect(self, async_client: AsyncOpencode) -> None:
command = await async_client.integration.command.connect(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
)
assert_matches_type(CommandConnectResponse, command, path=["response"])
@parametrize
async def test_method_connect_with_all_params(self, async_client: AsyncOpencode) -> None:
command = await async_client.integration.command.connect(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
label="label",
location={
"directory": "string",
@@ -277,8 +277,8 @@ async def test_method_connect_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_connect(self, async_client: AsyncOpencode) -> None:
response = await async_client.integration.command.with_raw_response.connect(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
)
assert response.is_closed is True
@@ -289,8 +289,8 @@ async def test_raw_response_connect(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_connect(self, async_client: AsyncOpencode) -> None:
async with async_client.integration.command.with_streaming_response.connect(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -305,22 +305,22 @@ async def test_path_params_connect(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
await async_client.integration.command.with_raw_response.connect(
integration_id="",
- method_id={},
+ method_id="methodID",
)
@parametrize
async def test_method_status(self, async_client: AsyncOpencode) -> None:
command = await async_client.integration.command.status(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert_matches_type(CommandStatusResponse, command, path=["response"])
@parametrize
async def test_method_status_with_all_params(self, async_client: AsyncOpencode) -> None:
command = await async_client.integration.command.status(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
location={
"directory": "string",
},
@@ -330,8 +330,8 @@ async def test_method_status_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_status(self, async_client: AsyncOpencode) -> None:
response = await async_client.integration.command.with_raw_response.status(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert response.is_closed is True
@@ -342,8 +342,8 @@ async def test_raw_response_status(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_status(self, async_client: AsyncOpencode) -> None:
async with async_client.integration.command.with_streaming_response.status(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -357,12 +357,12 @@ async def test_streaming_response_status(self, async_client: AsyncOpencode) -> N
async def test_path_params_status(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
await async_client.integration.command.with_raw_response.status(
- attempt_id={},
+ attempt_id="attemptID",
integration_id="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
await async_client.integration.command.with_raw_response.status(
attempt_id="",
- integration_id={},
+ integration_id="integrationID",
)
diff --git a/tests/api_resources/integration/test_connect.py b/tests/api_resources/integration/test_connect.py
index db30d20..0081969 100644
--- a/tests/api_resources/integration/test_connect.py
+++ b/tests/api_resources/integration/test_connect.py
@@ -18,16 +18,16 @@ class TestConnect:
@parametrize
def test_method_external(self, client: Opencode) -> None:
connect = client.integration.connect.external(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
)
assert connect is None
@parametrize
def test_method_external_with_all_params(self, client: Opencode) -> None:
connect = client.integration.connect.external(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
answer={},
label="label",
location={
@@ -39,8 +39,8 @@ def test_method_external_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_external(self, client: Opencode) -> None:
response = client.integration.connect.with_raw_response.external(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
)
assert response.is_closed is True
@@ -51,8 +51,8 @@ def test_raw_response_external(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_external(self, client: Opencode) -> None:
with client.integration.connect.with_streaming_response.external(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -67,13 +67,13 @@ def test_path_params_external(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
client.integration.connect.with_raw_response.external(
integration_id="",
- method_id={},
+ method_id="methodID",
)
@parametrize
def test_method_key(self, client: Opencode) -> None:
connect = client.integration.connect.key(
- integration_id={},
+ integration_id="integrationID",
key="key",
)
assert connect is None
@@ -81,7 +81,7 @@ def test_method_key(self, client: Opencode) -> None:
@parametrize
def test_method_key_with_all_params(self, client: Opencode) -> None:
connect = client.integration.connect.key(
- integration_id={},
+ integration_id="integrationID",
key="key",
answer={},
label="label",
@@ -94,7 +94,7 @@ def test_method_key_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_key(self, client: Opencode) -> None:
response = client.integration.connect.with_raw_response.key(
- integration_id={},
+ integration_id="integrationID",
key="key",
)
@@ -106,7 +106,7 @@ def test_raw_response_key(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_key(self, client: Opencode) -> None:
with client.integration.connect.with_streaming_response.key(
- integration_id={},
+ integration_id="integrationID",
key="key",
) as response:
assert not response.is_closed
@@ -134,16 +134,16 @@ class TestAsyncConnect:
@parametrize
async def test_method_external(self, async_client: AsyncOpencode) -> None:
connect = await async_client.integration.connect.external(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
)
assert connect is None
@parametrize
async def test_method_external_with_all_params(self, async_client: AsyncOpencode) -> None:
connect = await async_client.integration.connect.external(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
answer={},
label="label",
location={
@@ -155,8 +155,8 @@ async def test_method_external_with_all_params(self, async_client: AsyncOpencode
@parametrize
async def test_raw_response_external(self, async_client: AsyncOpencode) -> None:
response = await async_client.integration.connect.with_raw_response.external(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
)
assert response.is_closed is True
@@ -167,8 +167,8 @@ async def test_raw_response_external(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_external(self, async_client: AsyncOpencode) -> None:
async with async_client.integration.connect.with_streaming_response.external(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -183,13 +183,13 @@ async def test_path_params_external(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
await async_client.integration.connect.with_raw_response.external(
integration_id="",
- method_id={},
+ method_id="methodID",
)
@parametrize
async def test_method_key(self, async_client: AsyncOpencode) -> None:
connect = await async_client.integration.connect.key(
- integration_id={},
+ integration_id="integrationID",
key="key",
)
assert connect is None
@@ -197,7 +197,7 @@ async def test_method_key(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_key_with_all_params(self, async_client: AsyncOpencode) -> None:
connect = await async_client.integration.connect.key(
- integration_id={},
+ integration_id="integrationID",
key="key",
answer={},
label="label",
@@ -210,7 +210,7 @@ async def test_method_key_with_all_params(self, async_client: AsyncOpencode) ->
@parametrize
async def test_raw_response_key(self, async_client: AsyncOpencode) -> None:
response = await async_client.integration.connect.with_raw_response.key(
- integration_id={},
+ integration_id="integrationID",
key="key",
)
@@ -222,7 +222,7 @@ async def test_raw_response_key(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_key(self, async_client: AsyncOpencode) -> None:
async with async_client.integration.connect.with_streaming_response.key(
- integration_id={},
+ integration_id="integrationID",
key="key",
) as response:
assert not response.is_closed
diff --git a/tests/api_resources/integration/test_oauth.py b/tests/api_resources/integration/test_oauth.py
index 6892cc7..c48eef9 100644
--- a/tests/api_resources/integration/test_oauth.py
+++ b/tests/api_resources/integration/test_oauth.py
@@ -20,16 +20,16 @@ class TestOauth:
@parametrize
def test_method_cancel(self, client: Opencode) -> None:
oauth = client.integration.oauth.cancel(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert oauth is None
@parametrize
def test_method_cancel_with_all_params(self, client: Opencode) -> None:
oauth = client.integration.oauth.cancel(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
location={
"directory": "string",
},
@@ -39,8 +39,8 @@ def test_method_cancel_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_cancel(self, client: Opencode) -> None:
response = client.integration.oauth.with_raw_response.cancel(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert response.is_closed is True
@@ -51,8 +51,8 @@ def test_raw_response_cancel(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_cancel(self, client: Opencode) -> None:
with client.integration.oauth.with_streaming_response.cancel(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -66,29 +66,29 @@ def test_streaming_response_cancel(self, client: Opencode) -> None:
def test_path_params_cancel(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
client.integration.oauth.with_raw_response.cancel(
- attempt_id={},
+ attempt_id="attemptID",
integration_id="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
client.integration.oauth.with_raw_response.cancel(
attempt_id="",
- integration_id={},
+ integration_id="integrationID",
)
@parametrize
def test_method_complete(self, client: Opencode) -> None:
oauth = client.integration.oauth.complete(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert oauth is None
@parametrize
def test_method_complete_with_all_params(self, client: Opencode) -> None:
oauth = client.integration.oauth.complete(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
code="code",
location={
"directory": "string",
@@ -99,8 +99,8 @@ def test_method_complete_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_complete(self, client: Opencode) -> None:
response = client.integration.oauth.with_raw_response.complete(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert response.is_closed is True
@@ -111,8 +111,8 @@ def test_raw_response_complete(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_complete(self, client: Opencode) -> None:
with client.integration.oauth.with_streaming_response.complete(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -126,29 +126,29 @@ def test_streaming_response_complete(self, client: Opencode) -> None:
def test_path_params_complete(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
client.integration.oauth.with_raw_response.complete(
- attempt_id={},
+ attempt_id="attemptID",
integration_id="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
client.integration.oauth.with_raw_response.complete(
attempt_id="",
- integration_id={},
+ integration_id="integrationID",
)
@parametrize
def test_method_connect(self, client: Opencode) -> None:
oauth = client.integration.oauth.connect(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
)
assert_matches_type(OauthConnectResponse, oauth, path=["response"])
@parametrize
def test_method_connect_with_all_params(self, client: Opencode) -> None:
oauth = client.integration.oauth.connect(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
answer={},
label="label",
location={
@@ -160,8 +160,8 @@ def test_method_connect_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_connect(self, client: Opencode) -> None:
response = client.integration.oauth.with_raw_response.connect(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
)
assert response.is_closed is True
@@ -172,8 +172,8 @@ def test_raw_response_connect(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_connect(self, client: Opencode) -> None:
with client.integration.oauth.with_streaming_response.connect(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -188,22 +188,22 @@ def test_path_params_connect(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
client.integration.oauth.with_raw_response.connect(
integration_id="",
- method_id={},
+ method_id="methodID",
)
@parametrize
def test_method_status(self, client: Opencode) -> None:
oauth = client.integration.oauth.status(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert_matches_type(OauthStatusResponse, oauth, path=["response"])
@parametrize
def test_method_status_with_all_params(self, client: Opencode) -> None:
oauth = client.integration.oauth.status(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
location={
"directory": "string",
},
@@ -213,8 +213,8 @@ def test_method_status_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_status(self, client: Opencode) -> None:
response = client.integration.oauth.with_raw_response.status(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert response.is_closed is True
@@ -225,8 +225,8 @@ def test_raw_response_status(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_status(self, client: Opencode) -> None:
with client.integration.oauth.with_streaming_response.status(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -240,14 +240,14 @@ def test_streaming_response_status(self, client: Opencode) -> None:
def test_path_params_status(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
client.integration.oauth.with_raw_response.status(
- attempt_id={},
+ attempt_id="attemptID",
integration_id="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
client.integration.oauth.with_raw_response.status(
attempt_id="",
- integration_id={},
+ integration_id="integrationID",
)
@@ -259,16 +259,16 @@ class TestAsyncOauth:
@parametrize
async def test_method_cancel(self, async_client: AsyncOpencode) -> None:
oauth = await async_client.integration.oauth.cancel(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert oauth is None
@parametrize
async def test_method_cancel_with_all_params(self, async_client: AsyncOpencode) -> None:
oauth = await async_client.integration.oauth.cancel(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
location={
"directory": "string",
},
@@ -278,8 +278,8 @@ async def test_method_cancel_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_cancel(self, async_client: AsyncOpencode) -> None:
response = await async_client.integration.oauth.with_raw_response.cancel(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert response.is_closed is True
@@ -290,8 +290,8 @@ async def test_raw_response_cancel(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_cancel(self, async_client: AsyncOpencode) -> None:
async with async_client.integration.oauth.with_streaming_response.cancel(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -305,29 +305,29 @@ async def test_streaming_response_cancel(self, async_client: AsyncOpencode) -> N
async def test_path_params_cancel(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
await async_client.integration.oauth.with_raw_response.cancel(
- attempt_id={},
+ attempt_id="attemptID",
integration_id="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
await async_client.integration.oauth.with_raw_response.cancel(
attempt_id="",
- integration_id={},
+ integration_id="integrationID",
)
@parametrize
async def test_method_complete(self, async_client: AsyncOpencode) -> None:
oauth = await async_client.integration.oauth.complete(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert oauth is None
@parametrize
async def test_method_complete_with_all_params(self, async_client: AsyncOpencode) -> None:
oauth = await async_client.integration.oauth.complete(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
code="code",
location={
"directory": "string",
@@ -338,8 +338,8 @@ async def test_method_complete_with_all_params(self, async_client: AsyncOpencode
@parametrize
async def test_raw_response_complete(self, async_client: AsyncOpencode) -> None:
response = await async_client.integration.oauth.with_raw_response.complete(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert response.is_closed is True
@@ -350,8 +350,8 @@ async def test_raw_response_complete(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_complete(self, async_client: AsyncOpencode) -> None:
async with async_client.integration.oauth.with_streaming_response.complete(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -365,29 +365,29 @@ async def test_streaming_response_complete(self, async_client: AsyncOpencode) ->
async def test_path_params_complete(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
await async_client.integration.oauth.with_raw_response.complete(
- attempt_id={},
+ attempt_id="attemptID",
integration_id="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
await async_client.integration.oauth.with_raw_response.complete(
attempt_id="",
- integration_id={},
+ integration_id="integrationID",
)
@parametrize
async def test_method_connect(self, async_client: AsyncOpencode) -> None:
oauth = await async_client.integration.oauth.connect(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
)
assert_matches_type(OauthConnectResponse, oauth, path=["response"])
@parametrize
async def test_method_connect_with_all_params(self, async_client: AsyncOpencode) -> None:
oauth = await async_client.integration.oauth.connect(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
answer={},
label="label",
location={
@@ -399,8 +399,8 @@ async def test_method_connect_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_connect(self, async_client: AsyncOpencode) -> None:
response = await async_client.integration.oauth.with_raw_response.connect(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
)
assert response.is_closed is True
@@ -411,8 +411,8 @@ async def test_raw_response_connect(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_connect(self, async_client: AsyncOpencode) -> None:
async with async_client.integration.oauth.with_streaming_response.connect(
- integration_id={},
- method_id={},
+ integration_id="integrationID",
+ method_id="methodID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -427,22 +427,22 @@ async def test_path_params_connect(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
await async_client.integration.oauth.with_raw_response.connect(
integration_id="",
- method_id={},
+ method_id="methodID",
)
@parametrize
async def test_method_status(self, async_client: AsyncOpencode) -> None:
oauth = await async_client.integration.oauth.status(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert_matches_type(OauthStatusResponse, oauth, path=["response"])
@parametrize
async def test_method_status_with_all_params(self, async_client: AsyncOpencode) -> None:
oauth = await async_client.integration.oauth.status(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
location={
"directory": "string",
},
@@ -452,8 +452,8 @@ async def test_method_status_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_status(self, async_client: AsyncOpencode) -> None:
response = await async_client.integration.oauth.with_raw_response.status(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
)
assert response.is_closed is True
@@ -464,8 +464,8 @@ async def test_raw_response_status(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_status(self, async_client: AsyncOpencode) -> None:
async with async_client.integration.oauth.with_streaming_response.status(
- attempt_id={},
- integration_id={},
+ attempt_id="attemptID",
+ integration_id="integrationID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -479,12 +479,12 @@ async def test_streaming_response_status(self, async_client: AsyncOpencode) -> N
async def test_path_params_status(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `integration_id` but received ''"):
await async_client.integration.oauth.with_raw_response.status(
- attempt_id={},
+ attempt_id="attemptID",
integration_id="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `attempt_id` but received ''"):
await async_client.integration.oauth.with_raw_response.status(
attempt_id="",
- integration_id={},
+ integration_id="integrationID",
)
diff --git a/tests/api_resources/permission/test_saved.py b/tests/api_resources/permission/test_saved.py
index 31d6f0d..12711b4 100644
--- a/tests/api_resources/permission/test_saved.py
+++ b/tests/api_resources/permission/test_saved.py
@@ -21,14 +21,14 @@ class TestSaved:
@parametrize
def test_method_update(self, client: Opencode) -> None:
saved = client.permission.saved.update(
- id={},
+ id="id",
)
assert_matches_type(PermissionSavedInfo, saved, path=["response"])
@parametrize
def test_method_update_with_all_params(self, client: Opencode) -> None:
saved = client.permission.saved.update(
- id={},
+ id="id",
action="action",
resource="resource",
)
@@ -37,7 +37,7 @@ def test_method_update_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_update(self, client: Opencode) -> None:
response = client.permission.saved.with_raw_response.update(
- id={},
+ id="id",
)
assert response.is_closed is True
@@ -48,7 +48,7 @@ def test_raw_response_update(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_update(self, client: Opencode) -> None:
with client.permission.saved.with_streaming_response.update(
- id={},
+ id="id",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -100,14 +100,14 @@ def test_streaming_response_list(self, client: Opencode) -> None:
@parametrize
def test_method_remove(self, client: Opencode) -> None:
saved = client.permission.saved.remove(
- {},
+ "id",
)
assert saved is None
@parametrize
def test_raw_response_remove(self, client: Opencode) -> None:
response = client.permission.saved.with_raw_response.remove(
- {},
+ "id",
)
assert response.is_closed is True
@@ -118,7 +118,7 @@ def test_raw_response_remove(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_remove(self, client: Opencode) -> None:
with client.permission.saved.with_streaming_response.remove(
- {},
+ "id",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -144,14 +144,14 @@ class TestAsyncSaved:
@parametrize
async def test_method_update(self, async_client: AsyncOpencode) -> None:
saved = await async_client.permission.saved.update(
- id={},
+ id="id",
)
assert_matches_type(PermissionSavedInfo, saved, path=["response"])
@parametrize
async def test_method_update_with_all_params(self, async_client: AsyncOpencode) -> None:
saved = await async_client.permission.saved.update(
- id={},
+ id="id",
action="action",
resource="resource",
)
@@ -160,7 +160,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
response = await async_client.permission.saved.with_raw_response.update(
- id={},
+ id="id",
)
assert response.is_closed is True
@@ -171,7 +171,7 @@ async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_update(self, async_client: AsyncOpencode) -> None:
async with async_client.permission.saved.with_streaming_response.update(
- id={},
+ id="id",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -223,14 +223,14 @@ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> Non
@parametrize
async def test_method_remove(self, async_client: AsyncOpencode) -> None:
saved = await async_client.permission.saved.remove(
- {},
+ "id",
)
assert saved is None
@parametrize
async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
response = await async_client.permission.saved.with_raw_response.remove(
- {},
+ "id",
)
assert response.is_closed is True
@@ -241,7 +241,7 @@ async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_remove(self, async_client: AsyncOpencode) -> None:
async with async_client.permission.saved.with_streaming_response.remove(
- {},
+ "id",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/pty/test_connect.py b/tests/api_resources/pty/test_connect.py
index 5ed18f6..edbbdc6 100644
--- a/tests/api_resources/pty/test_connect.py
+++ b/tests/api_resources/pty/test_connect.py
@@ -20,14 +20,14 @@ class TestConnect:
@parametrize
def test_method_token(self, client: Opencode) -> None:
connect = client.pty.connect.token(
- pty_id={},
+ pty_id="ptyID",
)
assert_matches_type(ConnectTokenResponse, connect, path=["response"])
@parametrize
def test_method_token_with_all_params(self, client: Opencode) -> None:
connect = client.pty.connect.token(
- pty_id={},
+ pty_id="ptyID",
location={
"directory": "string",
},
@@ -38,7 +38,7 @@ def test_method_token_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_token(self, client: Opencode) -> None:
response = client.pty.connect.with_raw_response.token(
- pty_id={},
+ pty_id="ptyID",
)
assert response.is_closed is True
@@ -49,7 +49,7 @@ def test_raw_response_token(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_token(self, client: Opencode) -> None:
with client.pty.connect.with_streaming_response.token(
- pty_id={},
+ pty_id="ptyID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -75,14 +75,14 @@ class TestAsyncConnect:
@parametrize
async def test_method_token(self, async_client: AsyncOpencode) -> None:
connect = await async_client.pty.connect.token(
- pty_id={},
+ pty_id="ptyID",
)
assert_matches_type(ConnectTokenResponse, connect, path=["response"])
@parametrize
async def test_method_token_with_all_params(self, async_client: AsyncOpencode) -> None:
connect = await async_client.pty.connect.token(
- pty_id={},
+ pty_id="ptyID",
location={
"directory": "string",
},
@@ -93,7 +93,7 @@ async def test_method_token_with_all_params(self, async_client: AsyncOpencode) -
@parametrize
async def test_raw_response_token(self, async_client: AsyncOpencode) -> None:
response = await async_client.pty.connect.with_raw_response.token(
- pty_id={},
+ pty_id="ptyID",
)
assert response.is_closed is True
@@ -104,7 +104,7 @@ async def test_raw_response_token(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_token(self, async_client: AsyncOpencode) -> None:
async with async_client.pty.connect.with_streaming_response.token(
- pty_id={},
+ pty_id="ptyID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/session/instructions/test_entry.py b/tests/api_resources/session/instructions/test_entry.py
index e698ff3..3e9ab09 100644
--- a/tests/api_resources/session/instructions/test_entry.py
+++ b/tests/api_resources/session/instructions/test_entry.py
@@ -20,14 +20,14 @@ class TestEntry:
@parametrize
def test_method_list(self, client: Opencode) -> None:
entry = client.session.instructions.entry.list(
- {},
+ "sessionID",
)
assert_matches_type(EntryListResponse, entry, path=["response"])
@parametrize
def test_raw_response_list(self, client: Opencode) -> None:
response = client.session.instructions.entry.with_raw_response.list(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -38,7 +38,7 @@ def test_raw_response_list(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_list(self, client: Opencode) -> None:
with client.session.instructions.entry.with_streaming_response.list(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -58,8 +58,8 @@ def test_path_params_list(self, client: Opencode) -> None:
@parametrize
def test_method_put(self, client: Opencode) -> None:
entry = client.session.instructions.entry.put(
- key={},
- session_id={},
+ key="key",
+ session_id="sessionID",
value={},
)
assert entry is None
@@ -67,8 +67,8 @@ def test_method_put(self, client: Opencode) -> None:
@parametrize
def test_raw_response_put(self, client: Opencode) -> None:
response = client.session.instructions.entry.with_raw_response.put(
- key={},
- session_id={},
+ key="key",
+ session_id="sessionID",
value={},
)
@@ -80,8 +80,8 @@ def test_raw_response_put(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_put(self, client: Opencode) -> None:
with client.session.instructions.entry.with_streaming_response.put(
- key={},
- session_id={},
+ key="key",
+ session_id="sessionID",
value={},
) as response:
assert not response.is_closed
@@ -96,7 +96,7 @@ def test_streaming_response_put(self, client: Opencode) -> None:
def test_path_params_put(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
client.session.instructions.entry.with_raw_response.put(
- key={},
+ key="key",
session_id="",
value={},
)
@@ -104,23 +104,23 @@ def test_path_params_put(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
client.session.instructions.entry.with_raw_response.put(
key="",
- session_id={},
+ session_id="sessionID",
value={},
)
@parametrize
def test_method_remove(self, client: Opencode) -> None:
entry = client.session.instructions.entry.remove(
- key={},
- session_id={},
+ key="key",
+ session_id="sessionID",
)
assert entry is None
@parametrize
def test_raw_response_remove(self, client: Opencode) -> None:
response = client.session.instructions.entry.with_raw_response.remove(
- key={},
- session_id={},
+ key="key",
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -131,8 +131,8 @@ def test_raw_response_remove(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_remove(self, client: Opencode) -> None:
with client.session.instructions.entry.with_streaming_response.remove(
- key={},
- session_id={},
+ key="key",
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -146,14 +146,14 @@ def test_streaming_response_remove(self, client: Opencode) -> None:
def test_path_params_remove(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
client.session.instructions.entry.with_raw_response.remove(
- key={},
+ key="key",
session_id="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
client.session.instructions.entry.with_raw_response.remove(
key="",
- session_id={},
+ session_id="sessionID",
)
@@ -165,14 +165,14 @@ class TestAsyncEntry:
@parametrize
async def test_method_list(self, async_client: AsyncOpencode) -> None:
entry = await async_client.session.instructions.entry.list(
- {},
+ "sessionID",
)
assert_matches_type(EntryListResponse, entry, path=["response"])
@parametrize
async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.instructions.entry.with_raw_response.list(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -183,7 +183,7 @@ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
async with async_client.session.instructions.entry.with_streaming_response.list(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -203,8 +203,8 @@ async def test_path_params_list(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_put(self, async_client: AsyncOpencode) -> None:
entry = await async_client.session.instructions.entry.put(
- key={},
- session_id={},
+ key="key",
+ session_id="sessionID",
value={},
)
assert entry is None
@@ -212,8 +212,8 @@ async def test_method_put(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_raw_response_put(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.instructions.entry.with_raw_response.put(
- key={},
- session_id={},
+ key="key",
+ session_id="sessionID",
value={},
)
@@ -225,8 +225,8 @@ async def test_raw_response_put(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_put(self, async_client: AsyncOpencode) -> None:
async with async_client.session.instructions.entry.with_streaming_response.put(
- key={},
- session_id={},
+ key="key",
+ session_id="sessionID",
value={},
) as response:
assert not response.is_closed
@@ -241,7 +241,7 @@ async def test_streaming_response_put(self, async_client: AsyncOpencode) -> None
async def test_path_params_put(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
await async_client.session.instructions.entry.with_raw_response.put(
- key={},
+ key="key",
session_id="",
value={},
)
@@ -249,23 +249,23 @@ async def test_path_params_put(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
await async_client.session.instructions.entry.with_raw_response.put(
key="",
- session_id={},
+ session_id="sessionID",
value={},
)
@parametrize
async def test_method_remove(self, async_client: AsyncOpencode) -> None:
entry = await async_client.session.instructions.entry.remove(
- key={},
- session_id={},
+ key="key",
+ session_id="sessionID",
)
assert entry is None
@parametrize
async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.instructions.entry.with_raw_response.remove(
- key={},
- session_id={},
+ key="key",
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -276,8 +276,8 @@ async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_remove(self, async_client: AsyncOpencode) -> None:
async with async_client.session.instructions.entry.with_streaming_response.remove(
- key={},
- session_id={},
+ key="key",
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -291,12 +291,12 @@ async def test_streaming_response_remove(self, async_client: AsyncOpencode) -> N
async def test_path_params_remove(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
await async_client.session.instructions.entry.with_raw_response.remove(
- key={},
+ key="key",
session_id="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `key` but received ''"):
await async_client.session.instructions.entry.with_raw_response.remove(
key="",
- session_id={},
+ session_id="sessionID",
)
diff --git a/tests/api_resources/session/test_form.py b/tests/api_resources/session/test_form.py
index 884db76..463774c 100644
--- a/tests/api_resources/session/test_form.py
+++ b/tests/api_resources/session/test_form.py
@@ -22,7 +22,7 @@ class TestForm:
def test_method_create(self, client: Opencode) -> None:
form = client.session.form.create(
session_id="sessionID",
- fields={},
+ fields=[{}],
title="title",
)
assert_matches_type(FormInfo, form, path=["response"])
@@ -31,10 +31,10 @@ def test_method_create(self, client: Opencode) -> None:
def test_method_create_with_all_params(self, client: Opencode) -> None:
form = client.session.form.create(
session_id="sessionID",
- fields={},
+ fields=[{}],
title="title",
id={},
- metadata={},
+ metadata={"foo": {}},
)
assert_matches_type(FormInfo, form, path=["response"])
@@ -42,7 +42,7 @@ def test_method_create_with_all_params(self, client: Opencode) -> None:
def test_raw_response_create(self, client: Opencode) -> None:
response = client.session.form.with_raw_response.create(
session_id="sessionID",
- fields={},
+ fields=[{}],
title="title",
)
@@ -55,7 +55,7 @@ def test_raw_response_create(self, client: Opencode) -> None:
def test_streaming_response_create(self, client: Opencode) -> None:
with client.session.form.with_streaming_response.create(
session_id="sessionID",
- fields={},
+ fields=[{}],
title="title",
) as response:
assert not response.is_closed
@@ -71,7 +71,7 @@ def test_path_params_create(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
client.session.form.with_raw_response.create(
session_id="",
- fields={},
+ fields=[{}],
title="title",
)
@@ -116,7 +116,7 @@ def test_path_params_list(self, client: Opencode) -> None:
@parametrize
def test_method_cancel(self, client: Opencode) -> None:
form = client.session.form.cancel(
- form_id={},
+ form_id="formID",
session_id="sessionID",
)
assert form is None
@@ -124,7 +124,7 @@ def test_method_cancel(self, client: Opencode) -> None:
@parametrize
def test_method_cancel_with_all_params(self, client: Opencode) -> None:
form = client.session.form.cancel(
- form_id={},
+ form_id="formID",
session_id="sessionID",
message="message",
)
@@ -133,7 +133,7 @@ def test_method_cancel_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_cancel(self, client: Opencode) -> None:
response = client.session.form.with_raw_response.cancel(
- form_id={},
+ form_id="formID",
session_id="sessionID",
)
@@ -145,7 +145,7 @@ def test_raw_response_cancel(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_cancel(self, client: Opencode) -> None:
with client.session.form.with_streaming_response.cancel(
- form_id={},
+ form_id="formID",
session_id="sessionID",
) as response:
assert not response.is_closed
@@ -160,7 +160,7 @@ def test_streaming_response_cancel(self, client: Opencode) -> None:
def test_path_params_cancel(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
client.session.form.with_raw_response.cancel(
- form_id={},
+ form_id="formID",
session_id="",
)
@@ -173,7 +173,7 @@ def test_path_params_cancel(self, client: Opencode) -> None:
@parametrize
def test_method_get(self, client: Opencode) -> None:
form = client.session.form.get(
- form_id={},
+ form_id="formID",
session_id="sessionID",
)
assert_matches_type(FormDetail, form, path=["response"])
@@ -181,7 +181,7 @@ def test_method_get(self, client: Opencode) -> None:
@parametrize
def test_raw_response_get(self, client: Opencode) -> None:
response = client.session.form.with_raw_response.get(
- form_id={},
+ form_id="formID",
session_id="sessionID",
)
@@ -193,7 +193,7 @@ def test_raw_response_get(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_get(self, client: Opencode) -> None:
with client.session.form.with_streaming_response.get(
- form_id={},
+ form_id="formID",
session_id="sessionID",
) as response:
assert not response.is_closed
@@ -208,7 +208,7 @@ def test_streaming_response_get(self, client: Opencode) -> None:
def test_path_params_get(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
client.session.form.with_raw_response.get(
- form_id={},
+ form_id="formID",
session_id="",
)
@@ -221,18 +221,18 @@ def test_path_params_get(self, client: Opencode) -> None:
@parametrize
def test_method_reply(self, client: Opencode) -> None:
form = client.session.form.reply(
- form_id={},
+ form_id="formID",
session_id="sessionID",
- answer={},
+ answer={"foo": {}},
)
assert form is None
@parametrize
def test_raw_response_reply(self, client: Opencode) -> None:
response = client.session.form.with_raw_response.reply(
- form_id={},
+ form_id="formID",
session_id="sessionID",
- answer={},
+ answer={"foo": {}},
)
assert response.is_closed is True
@@ -243,9 +243,9 @@ def test_raw_response_reply(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_reply(self, client: Opencode) -> None:
with client.session.form.with_streaming_response.reply(
- form_id={},
+ form_id="formID",
session_id="sessionID",
- answer={},
+ answer={"foo": {}},
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -259,16 +259,16 @@ def test_streaming_response_reply(self, client: Opencode) -> None:
def test_path_params_reply(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
client.session.form.with_raw_response.reply(
- form_id={},
+ form_id="formID",
session_id="",
- answer={},
+ answer={"foo": {}},
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `form_id` but received ''"):
client.session.form.with_raw_response.reply(
form_id="",
session_id="sessionID",
- answer={},
+ answer={"foo": {}},
)
@@ -281,7 +281,7 @@ class TestAsyncForm:
async def test_method_create(self, async_client: AsyncOpencode) -> None:
form = await async_client.session.form.create(
session_id="sessionID",
- fields={},
+ fields=[{}],
title="title",
)
assert_matches_type(FormInfo, form, path=["response"])
@@ -290,10 +290,10 @@ async def test_method_create(self, async_client: AsyncOpencode) -> None:
async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
form = await async_client.session.form.create(
session_id="sessionID",
- fields={},
+ fields=[{}],
title="title",
id={},
- metadata={},
+ metadata={"foo": {}},
)
assert_matches_type(FormInfo, form, path=["response"])
@@ -301,7 +301,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpencode)
async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.form.with_raw_response.create(
session_id="sessionID",
- fields={},
+ fields=[{}],
title="title",
)
@@ -314,7 +314,7 @@ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
async with async_client.session.form.with_streaming_response.create(
session_id="sessionID",
- fields={},
+ fields=[{}],
title="title",
) as response:
assert not response.is_closed
@@ -330,7 +330,7 @@ async def test_path_params_create(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
await async_client.session.form.with_raw_response.create(
session_id="",
- fields={},
+ fields=[{}],
title="title",
)
@@ -375,7 +375,7 @@ async def test_path_params_list(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_cancel(self, async_client: AsyncOpencode) -> None:
form = await async_client.session.form.cancel(
- form_id={},
+ form_id="formID",
session_id="sessionID",
)
assert form is None
@@ -383,7 +383,7 @@ async def test_method_cancel(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_cancel_with_all_params(self, async_client: AsyncOpencode) -> None:
form = await async_client.session.form.cancel(
- form_id={},
+ form_id="formID",
session_id="sessionID",
message="message",
)
@@ -392,7 +392,7 @@ async def test_method_cancel_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_cancel(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.form.with_raw_response.cancel(
- form_id={},
+ form_id="formID",
session_id="sessionID",
)
@@ -404,7 +404,7 @@ async def test_raw_response_cancel(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_cancel(self, async_client: AsyncOpencode) -> None:
async with async_client.session.form.with_streaming_response.cancel(
- form_id={},
+ form_id="formID",
session_id="sessionID",
) as response:
assert not response.is_closed
@@ -419,7 +419,7 @@ async def test_streaming_response_cancel(self, async_client: AsyncOpencode) -> N
async def test_path_params_cancel(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
await async_client.session.form.with_raw_response.cancel(
- form_id={},
+ form_id="formID",
session_id="",
)
@@ -432,7 +432,7 @@ async def test_path_params_cancel(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_get(self, async_client: AsyncOpencode) -> None:
form = await async_client.session.form.get(
- form_id={},
+ form_id="formID",
session_id="sessionID",
)
assert_matches_type(FormDetail, form, path=["response"])
@@ -440,7 +440,7 @@ async def test_method_get(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.form.with_raw_response.get(
- form_id={},
+ form_id="formID",
session_id="sessionID",
)
@@ -452,7 +452,7 @@ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
async with async_client.session.form.with_streaming_response.get(
- form_id={},
+ form_id="formID",
session_id="sessionID",
) as response:
assert not response.is_closed
@@ -467,7 +467,7 @@ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None
async def test_path_params_get(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
await async_client.session.form.with_raw_response.get(
- form_id={},
+ form_id="formID",
session_id="",
)
@@ -480,18 +480,18 @@ async def test_path_params_get(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_reply(self, async_client: AsyncOpencode) -> None:
form = await async_client.session.form.reply(
- form_id={},
+ form_id="formID",
session_id="sessionID",
- answer={},
+ answer={"foo": {}},
)
assert form is None
@parametrize
async def test_raw_response_reply(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.form.with_raw_response.reply(
- form_id={},
+ form_id="formID",
session_id="sessionID",
- answer={},
+ answer={"foo": {}},
)
assert response.is_closed is True
@@ -502,9 +502,9 @@ async def test_raw_response_reply(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_reply(self, async_client: AsyncOpencode) -> None:
async with async_client.session.form.with_streaming_response.reply(
- form_id={},
+ form_id="formID",
session_id="sessionID",
- answer={},
+ answer={"foo": {}},
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -518,14 +518,14 @@ async def test_streaming_response_reply(self, async_client: AsyncOpencode) -> No
async def test_path_params_reply(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
await async_client.session.form.with_raw_response.reply(
- form_id={},
+ form_id="formID",
session_id="",
- answer={},
+ answer={"foo": {}},
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `form_id` but received ''"):
await async_client.session.form.with_raw_response.reply(
form_id="",
session_id="sessionID",
- answer={},
+ answer={"foo": {}},
)
diff --git a/tests/api_resources/session/test_inbox.py b/tests/api_resources/session/test_inbox.py
index 738071a..51d9997 100644
--- a/tests/api_resources/session/test_inbox.py
+++ b/tests/api_resources/session/test_inbox.py
@@ -20,18 +20,18 @@ class TestInbox:
@parametrize
def test_method_update(self, client: Opencode) -> None:
inbox = client.session.inbox.update(
- inbox_id={},
- session_id={},
- delivery={},
+ inbox_id="inboxID",
+ session_id="sessionID",
+ delivery="steer",
)
assert inbox is None
@parametrize
def test_raw_response_update(self, client: Opencode) -> None:
response = client.session.inbox.with_raw_response.update(
- inbox_id={},
- session_id={},
- delivery={},
+ inbox_id="inboxID",
+ session_id="sessionID",
+ delivery="steer",
)
assert response.is_closed is True
@@ -42,9 +42,9 @@ def test_raw_response_update(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_update(self, client: Opencode) -> None:
with client.session.inbox.with_streaming_response.update(
- inbox_id={},
- session_id={},
- delivery={},
+ inbox_id="inboxID",
+ session_id="sessionID",
+ delivery="steer",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -58,29 +58,29 @@ def test_streaming_response_update(self, client: Opencode) -> None:
def test_path_params_update(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
client.session.inbox.with_raw_response.update(
- inbox_id={},
+ inbox_id="inboxID",
session_id="",
- delivery={},
+ delivery="steer",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `inbox_id` but received ''"):
client.session.inbox.with_raw_response.update(
inbox_id="",
- session_id={},
- delivery={},
+ session_id="sessionID",
+ delivery="steer",
)
@parametrize
def test_method_list(self, client: Opencode) -> None:
inbox = client.session.inbox.list(
- {},
+ "sessionID",
)
assert_matches_type(InboxListResponse, inbox, path=["response"])
@parametrize
def test_raw_response_list(self, client: Opencode) -> None:
response = client.session.inbox.with_raw_response.list(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -91,7 +91,7 @@ def test_raw_response_list(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_list(self, client: Opencode) -> None:
with client.session.inbox.with_streaming_response.list(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -111,16 +111,16 @@ def test_path_params_list(self, client: Opencode) -> None:
@parametrize
def test_method_cancel(self, client: Opencode) -> None:
inbox = client.session.inbox.cancel(
- inbox_id={},
- session_id={},
+ inbox_id="inboxID",
+ session_id="sessionID",
)
assert inbox is None
@parametrize
def test_raw_response_cancel(self, client: Opencode) -> None:
response = client.session.inbox.with_raw_response.cancel(
- inbox_id={},
- session_id={},
+ inbox_id="inboxID",
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -131,8 +131,8 @@ def test_raw_response_cancel(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_cancel(self, client: Opencode) -> None:
with client.session.inbox.with_streaming_response.cancel(
- inbox_id={},
- session_id={},
+ inbox_id="inboxID",
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -146,14 +146,14 @@ def test_streaming_response_cancel(self, client: Opencode) -> None:
def test_path_params_cancel(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
client.session.inbox.with_raw_response.cancel(
- inbox_id={},
+ inbox_id="inboxID",
session_id="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `inbox_id` but received ''"):
client.session.inbox.with_raw_response.cancel(
inbox_id="",
- session_id={},
+ session_id="sessionID",
)
@@ -165,18 +165,18 @@ class TestAsyncInbox:
@parametrize
async def test_method_update(self, async_client: AsyncOpencode) -> None:
inbox = await async_client.session.inbox.update(
- inbox_id={},
- session_id={},
- delivery={},
+ inbox_id="inboxID",
+ session_id="sessionID",
+ delivery="steer",
)
assert inbox is None
@parametrize
async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.inbox.with_raw_response.update(
- inbox_id={},
- session_id={},
- delivery={},
+ inbox_id="inboxID",
+ session_id="sessionID",
+ delivery="steer",
)
assert response.is_closed is True
@@ -187,9 +187,9 @@ async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_update(self, async_client: AsyncOpencode) -> None:
async with async_client.session.inbox.with_streaming_response.update(
- inbox_id={},
- session_id={},
- delivery={},
+ inbox_id="inboxID",
+ session_id="sessionID",
+ delivery="steer",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -203,29 +203,29 @@ async def test_streaming_response_update(self, async_client: AsyncOpencode) -> N
async def test_path_params_update(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
await async_client.session.inbox.with_raw_response.update(
- inbox_id={},
+ inbox_id="inboxID",
session_id="",
- delivery={},
+ delivery="steer",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `inbox_id` but received ''"):
await async_client.session.inbox.with_raw_response.update(
inbox_id="",
- session_id={},
- delivery={},
+ session_id="sessionID",
+ delivery="steer",
)
@parametrize
async def test_method_list(self, async_client: AsyncOpencode) -> None:
inbox = await async_client.session.inbox.list(
- {},
+ "sessionID",
)
assert_matches_type(InboxListResponse, inbox, path=["response"])
@parametrize
async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.inbox.with_raw_response.list(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -236,7 +236,7 @@ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
async with async_client.session.inbox.with_streaming_response.list(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -256,16 +256,16 @@ async def test_path_params_list(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_cancel(self, async_client: AsyncOpencode) -> None:
inbox = await async_client.session.inbox.cancel(
- inbox_id={},
- session_id={},
+ inbox_id="inboxID",
+ session_id="sessionID",
)
assert inbox is None
@parametrize
async def test_raw_response_cancel(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.inbox.with_raw_response.cancel(
- inbox_id={},
- session_id={},
+ inbox_id="inboxID",
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -276,8 +276,8 @@ async def test_raw_response_cancel(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_cancel(self, async_client: AsyncOpencode) -> None:
async with async_client.session.inbox.with_streaming_response.cancel(
- inbox_id={},
- session_id={},
+ inbox_id="inboxID",
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -291,12 +291,12 @@ async def test_streaming_response_cancel(self, async_client: AsyncOpencode) -> N
async def test_path_params_cancel(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
await async_client.session.inbox.with_raw_response.cancel(
- inbox_id={},
+ inbox_id="inboxID",
session_id="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `inbox_id` but received ''"):
await async_client.session.inbox.with_raw_response.cancel(
inbox_id="",
- session_id={},
+ session_id="sessionID",
)
diff --git a/tests/api_resources/session/test_message.py b/tests/api_resources/session/test_message.py
index 715e0d6..3a4dd09 100644
--- a/tests/api_resources/session/test_message.py
+++ b/tests/api_resources/session/test_message.py
@@ -20,16 +20,16 @@ class TestMessage:
@parametrize
def test_method_get(self, client: Opencode) -> None:
message = client.session.message.get(
- message_id={},
- session_id={},
+ message_id="messageID",
+ session_id="sessionID",
)
assert_matches_type(SessionMessageInfo, message, path=["response"])
@parametrize
def test_raw_response_get(self, client: Opencode) -> None:
response = client.session.message.with_raw_response.get(
- message_id={},
- session_id={},
+ message_id="messageID",
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -40,8 +40,8 @@ def test_raw_response_get(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_get(self, client: Opencode) -> None:
with client.session.message.with_streaming_response.get(
- message_id={},
- session_id={},
+ message_id="messageID",
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -55,14 +55,14 @@ def test_streaming_response_get(self, client: Opencode) -> None:
def test_path_params_get(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
client.session.message.with_raw_response.get(
- message_id={},
+ message_id="messageID",
session_id="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"):
client.session.message.with_raw_response.get(
message_id="",
- session_id={},
+ session_id="sessionID",
)
@@ -74,16 +74,16 @@ class TestAsyncMessage:
@parametrize
async def test_method_get(self, async_client: AsyncOpencode) -> None:
message = await async_client.session.message.get(
- message_id={},
- session_id={},
+ message_id="messageID",
+ session_id="sessionID",
)
assert_matches_type(SessionMessageInfo, message, path=["response"])
@parametrize
async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.message.with_raw_response.get(
- message_id={},
- session_id={},
+ message_id="messageID",
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -94,8 +94,8 @@ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
async with async_client.session.message.with_streaming_response.get(
- message_id={},
- session_id={},
+ message_id="messageID",
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -109,12 +109,12 @@ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None
async def test_path_params_get(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
await async_client.session.message.with_raw_response.get(
- message_id={},
+ message_id="messageID",
session_id="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"):
await async_client.session.message.with_raw_response.get(
message_id="",
- session_id={},
+ session_id="sessionID",
)
diff --git a/tests/api_resources/session/test_revert.py b/tests/api_resources/session/test_revert.py
index 6b53689..db4c560 100644
--- a/tests/api_resources/session/test_revert.py
+++ b/tests/api_resources/session/test_revert.py
@@ -20,14 +20,14 @@ class TestRevert:
@parametrize
def test_method_clear(self, client: Opencode) -> None:
revert = client.session.revert.clear(
- {},
+ "sessionID",
)
assert revert is None
@parametrize
def test_raw_response_clear(self, client: Opencode) -> None:
response = client.session.revert.with_raw_response.clear(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -38,7 +38,7 @@ def test_raw_response_clear(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_clear(self, client: Opencode) -> None:
with client.session.revert.with_streaming_response.clear(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -58,14 +58,14 @@ def test_path_params_clear(self, client: Opencode) -> None:
@parametrize
def test_method_commit(self, client: Opencode) -> None:
revert = client.session.revert.commit(
- {},
+ "sessionID",
)
assert revert is None
@parametrize
def test_raw_response_commit(self, client: Opencode) -> None:
response = client.session.revert.with_raw_response.commit(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -76,7 +76,7 @@ def test_raw_response_commit(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_commit(self, client: Opencode) -> None:
with client.session.revert.with_streaming_response.commit(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -96,16 +96,16 @@ def test_path_params_commit(self, client: Opencode) -> None:
@parametrize
def test_method_stage(self, client: Opencode) -> None:
revert = client.session.revert.stage(
- session_id={},
- message_id={},
+ session_id="sessionID",
+ message_id="messageID",
)
assert_matches_type(SessionRevert, revert, path=["response"])
@parametrize
def test_method_stage_with_all_params(self, client: Opencode) -> None:
revert = client.session.revert.stage(
- session_id={},
- message_id={},
+ session_id="sessionID",
+ message_id="messageID",
files=True,
)
assert_matches_type(SessionRevert, revert, path=["response"])
@@ -113,8 +113,8 @@ def test_method_stage_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_stage(self, client: Opencode) -> None:
response = client.session.revert.with_raw_response.stage(
- session_id={},
- message_id={},
+ session_id="sessionID",
+ message_id="messageID",
)
assert response.is_closed is True
@@ -125,8 +125,8 @@ def test_raw_response_stage(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_stage(self, client: Opencode) -> None:
with client.session.revert.with_streaming_response.stage(
- session_id={},
- message_id={},
+ session_id="sessionID",
+ message_id="messageID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -141,7 +141,7 @@ def test_path_params_stage(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
client.session.revert.with_raw_response.stage(
session_id="",
- message_id={},
+ message_id="messageID",
)
@@ -153,14 +153,14 @@ class TestAsyncRevert:
@parametrize
async def test_method_clear(self, async_client: AsyncOpencode) -> None:
revert = await async_client.session.revert.clear(
- {},
+ "sessionID",
)
assert revert is None
@parametrize
async def test_raw_response_clear(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.revert.with_raw_response.clear(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -171,7 +171,7 @@ async def test_raw_response_clear(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_clear(self, async_client: AsyncOpencode) -> None:
async with async_client.session.revert.with_streaming_response.clear(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -191,14 +191,14 @@ async def test_path_params_clear(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_commit(self, async_client: AsyncOpencode) -> None:
revert = await async_client.session.revert.commit(
- {},
+ "sessionID",
)
assert revert is None
@parametrize
async def test_raw_response_commit(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.revert.with_raw_response.commit(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -209,7 +209,7 @@ async def test_raw_response_commit(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_commit(self, async_client: AsyncOpencode) -> None:
async with async_client.session.revert.with_streaming_response.commit(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -229,16 +229,16 @@ async def test_path_params_commit(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_stage(self, async_client: AsyncOpencode) -> None:
revert = await async_client.session.revert.stage(
- session_id={},
- message_id={},
+ session_id="sessionID",
+ message_id="messageID",
)
assert_matches_type(SessionRevert, revert, path=["response"])
@parametrize
async def test_method_stage_with_all_params(self, async_client: AsyncOpencode) -> None:
revert = await async_client.session.revert.stage(
- session_id={},
- message_id={},
+ session_id="sessionID",
+ message_id="messageID",
files=True,
)
assert_matches_type(SessionRevert, revert, path=["response"])
@@ -246,8 +246,8 @@ async def test_method_stage_with_all_params(self, async_client: AsyncOpencode) -
@parametrize
async def test_raw_response_stage(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.revert.with_raw_response.stage(
- session_id={},
- message_id={},
+ session_id="sessionID",
+ message_id="messageID",
)
assert response.is_closed is True
@@ -258,8 +258,8 @@ async def test_raw_response_stage(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_stage(self, async_client: AsyncOpencode) -> None:
async with async_client.session.revert.with_streaming_response.stage(
- session_id={},
- message_id={},
+ session_id="sessionID",
+ message_id="messageID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -274,5 +274,5 @@ async def test_path_params_stage(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
await async_client.session.revert.with_raw_response.stage(
session_id="",
- message_id={},
+ message_id="messageID",
)
diff --git a/tests/api_resources/test_agent.py b/tests/api_resources/test_agent.py
index d7b9a35..7522be4 100644
--- a/tests/api_resources/test_agent.py
+++ b/tests/api_resources/test_agent.py
@@ -54,14 +54,14 @@ def test_streaming_response_list(self, client: Opencode) -> None:
@parametrize
def test_method_get(self, client: Opencode) -> None:
agent = client.agent.get(
- agent_id={},
+ agent_id="agentID",
)
assert_matches_type(AgentGetResponse, agent, path=["response"])
@parametrize
def test_method_get_with_all_params(self, client: Opencode) -> None:
agent = client.agent.get(
- agent_id={},
+ agent_id="agentID",
location={
"directory": "string",
},
@@ -71,7 +71,7 @@ def test_method_get_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_get(self, client: Opencode) -> None:
response = client.agent.with_raw_response.get(
- agent_id={},
+ agent_id="agentID",
)
assert response.is_closed is True
@@ -82,7 +82,7 @@ def test_raw_response_get(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_get(self, client: Opencode) -> None:
with client.agent.with_streaming_response.get(
- agent_id={},
+ agent_id="agentID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -142,14 +142,14 @@ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> Non
@parametrize
async def test_method_get(self, async_client: AsyncOpencode) -> None:
agent = await async_client.agent.get(
- agent_id={},
+ agent_id="agentID",
)
assert_matches_type(AgentGetResponse, agent, path=["response"])
@parametrize
async def test_method_get_with_all_params(self, async_client: AsyncOpencode) -> None:
agent = await async_client.agent.get(
- agent_id={},
+ agent_id="agentID",
location={
"directory": "string",
},
@@ -159,7 +159,7 @@ async def test_method_get_with_all_params(self, async_client: AsyncOpencode) ->
@parametrize
async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
response = await async_client.agent.with_raw_response.get(
- agent_id={},
+ agent_id="agentID",
)
assert response.is_closed is True
@@ -170,7 +170,7 @@ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
async with async_client.agent.with_streaming_response.get(
- agent_id={},
+ agent_id="agentID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/test_credential.py b/tests/api_resources/test_credential.py
index f02b178..3ce575b 100644
--- a/tests/api_resources/test_credential.py
+++ b/tests/api_resources/test_credential.py
@@ -21,7 +21,7 @@ class TestCredential:
@parametrize
def test_method_create(self, client: Opencode) -> None:
credential = client.credential.create(
- integration_id={},
+ integration_id="integrationID",
value={},
)
assert_matches_type(CredentialEntry, credential, path=["response"])
@@ -29,10 +29,10 @@ def test_method_create(self, client: Opencode) -> None:
@parametrize
def test_method_create_with_all_params(self, client: Opencode) -> None:
credential = client.credential.create(
- integration_id={},
+ integration_id="integrationID",
value={},
activate=True,
- id={},
+ id="id",
label="label",
)
assert_matches_type(CredentialEntry, credential, path=["response"])
@@ -40,7 +40,7 @@ def test_method_create_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_create(self, client: Opencode) -> None:
response = client.credential.with_raw_response.create(
- integration_id={},
+ integration_id="integrationID",
value={},
)
@@ -52,7 +52,7 @@ def test_raw_response_create(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_create(self, client: Opencode) -> None:
with client.credential.with_streaming_response.create(
- integration_id={},
+ integration_id="integrationID",
value={},
) as response:
assert not response.is_closed
@@ -66,7 +66,7 @@ def test_streaming_response_create(self, client: Opencode) -> None:
@parametrize
def test_method_update(self, client: Opencode) -> None:
credential = client.credential.update(
- credential_id={},
+ credential_id="credentialID",
label="label",
)
assert credential is None
@@ -74,7 +74,7 @@ def test_method_update(self, client: Opencode) -> None:
@parametrize
def test_raw_response_update(self, client: Opencode) -> None:
response = client.credential.with_raw_response.update(
- credential_id={},
+ credential_id="credentialID",
label="label",
)
@@ -86,7 +86,7 @@ def test_raw_response_update(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_update(self, client: Opencode) -> None:
with client.credential.with_streaming_response.update(
- credential_id={},
+ credential_id="credentialID",
label="label",
) as response:
assert not response.is_closed
@@ -133,14 +133,14 @@ def test_streaming_response_list(self, client: Opencode) -> None:
@parametrize
def test_method_activate(self, client: Opencode) -> None:
credential = client.credential.activate(
- {},
+ "credentialID",
)
assert credential is None
@parametrize
def test_raw_response_activate(self, client: Opencode) -> None:
response = client.credential.with_raw_response.activate(
- {},
+ "credentialID",
)
assert response.is_closed is True
@@ -151,7 +151,7 @@ def test_raw_response_activate(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_activate(self, client: Opencode) -> None:
with client.credential.with_streaming_response.activate(
- {},
+ "credentialID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -171,14 +171,14 @@ def test_path_params_activate(self, client: Opencode) -> None:
@parametrize
def test_method_remove(self, client: Opencode) -> None:
credential = client.credential.remove(
- {},
+ "credentialID",
)
assert credential is None
@parametrize
def test_raw_response_remove(self, client: Opencode) -> None:
response = client.credential.with_raw_response.remove(
- {},
+ "credentialID",
)
assert response.is_closed is True
@@ -189,7 +189,7 @@ def test_raw_response_remove(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_remove(self, client: Opencode) -> None:
with client.credential.with_streaming_response.remove(
- {},
+ "credentialID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -215,7 +215,7 @@ class TestAsyncCredential:
@parametrize
async def test_method_create(self, async_client: AsyncOpencode) -> None:
credential = await async_client.credential.create(
- integration_id={},
+ integration_id="integrationID",
value={},
)
assert_matches_type(CredentialEntry, credential, path=["response"])
@@ -223,10 +223,10 @@ async def test_method_create(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
credential = await async_client.credential.create(
- integration_id={},
+ integration_id="integrationID",
value={},
activate=True,
- id={},
+ id="id",
label="label",
)
assert_matches_type(CredentialEntry, credential, path=["response"])
@@ -234,7 +234,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
response = await async_client.credential.with_raw_response.create(
- integration_id={},
+ integration_id="integrationID",
value={},
)
@@ -246,7 +246,7 @@ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
async with async_client.credential.with_streaming_response.create(
- integration_id={},
+ integration_id="integrationID",
value={},
) as response:
assert not response.is_closed
@@ -260,7 +260,7 @@ async def test_streaming_response_create(self, async_client: AsyncOpencode) -> N
@parametrize
async def test_method_update(self, async_client: AsyncOpencode) -> None:
credential = await async_client.credential.update(
- credential_id={},
+ credential_id="credentialID",
label="label",
)
assert credential is None
@@ -268,7 +268,7 @@ async def test_method_update(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
response = await async_client.credential.with_raw_response.update(
- credential_id={},
+ credential_id="credentialID",
label="label",
)
@@ -280,7 +280,7 @@ async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_update(self, async_client: AsyncOpencode) -> None:
async with async_client.credential.with_streaming_response.update(
- credential_id={},
+ credential_id="credentialID",
label="label",
) as response:
assert not response.is_closed
@@ -327,14 +327,14 @@ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> Non
@parametrize
async def test_method_activate(self, async_client: AsyncOpencode) -> None:
credential = await async_client.credential.activate(
- {},
+ "credentialID",
)
assert credential is None
@parametrize
async def test_raw_response_activate(self, async_client: AsyncOpencode) -> None:
response = await async_client.credential.with_raw_response.activate(
- {},
+ "credentialID",
)
assert response.is_closed is True
@@ -345,7 +345,7 @@ async def test_raw_response_activate(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_activate(self, async_client: AsyncOpencode) -> None:
async with async_client.credential.with_streaming_response.activate(
- {},
+ "credentialID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -365,14 +365,14 @@ async def test_path_params_activate(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_remove(self, async_client: AsyncOpencode) -> None:
credential = await async_client.credential.remove(
- {},
+ "credentialID",
)
assert credential is None
@parametrize
async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
response = await async_client.credential.with_raw_response.remove(
- {},
+ "credentialID",
)
assert response.is_closed is True
@@ -383,7 +383,7 @@ async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_remove(self, async_client: AsyncOpencode) -> None:
async with async_client.credential.with_streaming_response.remove(
- {},
+ "credentialID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/test_integration.py b/tests/api_resources/test_integration.py
index 59ed989..be245d7 100644
--- a/tests/api_resources/test_integration.py
+++ b/tests/api_resources/test_integration.py
@@ -54,14 +54,14 @@ def test_streaming_response_list(self, client: Opencode) -> None:
@parametrize
def test_method_get(self, client: Opencode) -> None:
integration = client.integration.get(
- integration_id={},
+ integration_id="integrationID",
)
assert_matches_type(IntegrationGetResponse, integration, path=["response"])
@parametrize
def test_method_get_with_all_params(self, client: Opencode) -> None:
integration = client.integration.get(
- integration_id={},
+ integration_id="integrationID",
location={
"directory": "string",
},
@@ -71,7 +71,7 @@ def test_method_get_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_get(self, client: Opencode) -> None:
response = client.integration.with_raw_response.get(
- integration_id={},
+ integration_id="integrationID",
)
assert response.is_closed is True
@@ -82,7 +82,7 @@ def test_raw_response_get(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_get(self, client: Opencode) -> None:
with client.integration.with_streaming_response.get(
- integration_id={},
+ integration_id="integrationID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -142,14 +142,14 @@ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> Non
@parametrize
async def test_method_get(self, async_client: AsyncOpencode) -> None:
integration = await async_client.integration.get(
- integration_id={},
+ integration_id="integrationID",
)
assert_matches_type(IntegrationGetResponse, integration, path=["response"])
@parametrize
async def test_method_get_with_all_params(self, async_client: AsyncOpencode) -> None:
integration = await async_client.integration.get(
- integration_id={},
+ integration_id="integrationID",
location={
"directory": "string",
},
@@ -159,7 +159,7 @@ async def test_method_get_with_all_params(self, async_client: AsyncOpencode) ->
@parametrize
async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
response = await async_client.integration.with_raw_response.get(
- integration_id={},
+ integration_id="integrationID",
)
assert response.is_closed is True
@@ -170,7 +170,7 @@ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
async with async_client.integration.with_streaming_response.get(
- integration_id={},
+ integration_id="integrationID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/test_message.py b/tests/api_resources/test_message.py
index ae9d857..f68869c 100644
--- a/tests/api_resources/test_message.py
+++ b/tests/api_resources/test_message.py
@@ -20,14 +20,14 @@ class TestMessage:
@parametrize
def test_method_list(self, client: Opencode) -> None:
message = client.message.list(
- session_id={},
+ session_id="sessionID",
)
assert_matches_type(SessionMessageInfo, message, path=["response"])
@parametrize
def test_method_list_with_all_params(self, client: Opencode) -> None:
message = client.message.list(
- session_id={},
+ session_id="sessionID",
cursor="cursor",
limit=0,
order="asc",
@@ -38,7 +38,7 @@ def test_method_list_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_list(self, client: Opencode) -> None:
response = client.message.with_raw_response.list(
- session_id={},
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -49,7 +49,7 @@ def test_raw_response_list(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_list(self, client: Opencode) -> None:
with client.message.with_streaming_response.list(
- session_id={},
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -75,14 +75,14 @@ class TestAsyncMessage:
@parametrize
async def test_method_list(self, async_client: AsyncOpencode) -> None:
message = await async_client.message.list(
- session_id={},
+ session_id="sessionID",
)
assert_matches_type(SessionMessageInfo, message, path=["response"])
@parametrize
async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
message = await async_client.message.list(
- session_id={},
+ session_id="sessionID",
cursor="cursor",
limit=0,
order="asc",
@@ -93,7 +93,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) ->
@parametrize
async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
response = await async_client.message.with_raw_response.list(
- session_id={},
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -104,7 +104,7 @@ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
async with async_client.message.with_streaming_response.list(
- session_id={},
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/test_permission.py b/tests/api_resources/test_permission.py
index 95c7d49..3a48cb5 100644
--- a/tests/api_resources/test_permission.py
+++ b/tests/api_resources/test_permission.py
@@ -21,7 +21,7 @@ class TestPermission:
@parametrize
def test_method_create(self, client: Opencode) -> None:
permission = client.permission.create(
- session_id={},
+ session_id="sessionID",
action="action",
resources=["string"],
)
@@ -30,7 +30,7 @@ def test_method_create(self, client: Opencode) -> None:
@parametrize
def test_method_create_with_all_params(self, client: Opencode) -> None:
permission = client.permission.create(
- session_id={},
+ session_id="sessionID",
action="action",
resources=["string"],
agent={},
@@ -44,7 +44,7 @@ def test_method_create_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_create(self, client: Opencode) -> None:
response = client.permission.with_raw_response.create(
- session_id={},
+ session_id="sessionID",
action="action",
resources=["string"],
)
@@ -57,7 +57,7 @@ def test_raw_response_create(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_create(self, client: Opencode) -> None:
with client.permission.with_streaming_response.create(
- session_id={},
+ session_id="sessionID",
action="action",
resources=["string"],
) as response:
@@ -81,14 +81,14 @@ def test_path_params_create(self, client: Opencode) -> None:
@parametrize
def test_method_list(self, client: Opencode) -> None:
permission = client.permission.list(
- {},
+ "sessionID",
)
assert_matches_type(PermissionListResponse, permission, path=["response"])
@parametrize
def test_raw_response_list(self, client: Opencode) -> None:
response = client.permission.with_raw_response.list(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -99,7 +99,7 @@ def test_raw_response_list(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_list(self, client: Opencode) -> None:
with client.permission.with_streaming_response.list(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -119,16 +119,16 @@ def test_path_params_list(self, client: Opencode) -> None:
@parametrize
def test_method_get(self, client: Opencode) -> None:
permission = client.permission.get(
- request_id={},
- session_id={},
+ request_id="requestID",
+ session_id="sessionID",
)
assert_matches_type(PermissionRequest, permission, path=["response"])
@parametrize
def test_raw_response_get(self, client: Opencode) -> None:
response = client.permission.with_raw_response.get(
- request_id={},
- session_id={},
+ request_id="requestID",
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -139,8 +139,8 @@ def test_raw_response_get(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_get(self, client: Opencode) -> None:
with client.permission.with_streaming_response.get(
- request_id={},
- session_id={},
+ request_id="requestID",
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -154,31 +154,31 @@ def test_streaming_response_get(self, client: Opencode) -> None:
def test_path_params_get(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
client.permission.with_raw_response.get(
- request_id={},
+ request_id="requestID",
session_id="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `request_id` but received ''"):
client.permission.with_raw_response.get(
request_id="",
- session_id={},
+ session_id="sessionID",
)
@parametrize
def test_method_reply(self, client: Opencode) -> None:
permission = client.permission.reply(
- request_id={},
- session_id={},
- decision={},
+ request_id="requestID",
+ session_id="sessionID",
+ decision="once",
)
assert permission is None
@parametrize
def test_method_reply_with_all_params(self, client: Opencode) -> None:
permission = client.permission.reply(
- request_id={},
- session_id={},
- decision={},
+ request_id="requestID",
+ session_id="sessionID",
+ decision="once",
message="message",
)
assert permission is None
@@ -186,9 +186,9 @@ def test_method_reply_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_reply(self, client: Opencode) -> None:
response = client.permission.with_raw_response.reply(
- request_id={},
- session_id={},
- decision={},
+ request_id="requestID",
+ session_id="sessionID",
+ decision="once",
)
assert response.is_closed is True
@@ -199,9 +199,9 @@ def test_raw_response_reply(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_reply(self, client: Opencode) -> None:
with client.permission.with_streaming_response.reply(
- request_id={},
- session_id={},
- decision={},
+ request_id="requestID",
+ session_id="sessionID",
+ decision="once",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -215,16 +215,16 @@ def test_streaming_response_reply(self, client: Opencode) -> None:
def test_path_params_reply(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
client.permission.with_raw_response.reply(
- request_id={},
+ request_id="requestID",
session_id="",
- decision={},
+ decision="once",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `request_id` but received ''"):
client.permission.with_raw_response.reply(
request_id="",
- session_id={},
- decision={},
+ session_id="sessionID",
+ decision="once",
)
@@ -236,7 +236,7 @@ class TestAsyncPermission:
@parametrize
async def test_method_create(self, async_client: AsyncOpencode) -> None:
permission = await async_client.permission.create(
- session_id={},
+ session_id="sessionID",
action="action",
resources=["string"],
)
@@ -245,7 +245,7 @@ async def test_method_create(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
permission = await async_client.permission.create(
- session_id={},
+ session_id="sessionID",
action="action",
resources=["string"],
agent={},
@@ -259,7 +259,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
response = await async_client.permission.with_raw_response.create(
- session_id={},
+ session_id="sessionID",
action="action",
resources=["string"],
)
@@ -272,7 +272,7 @@ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
async with async_client.permission.with_streaming_response.create(
- session_id={},
+ session_id="sessionID",
action="action",
resources=["string"],
) as response:
@@ -296,14 +296,14 @@ async def test_path_params_create(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_list(self, async_client: AsyncOpencode) -> None:
permission = await async_client.permission.list(
- {},
+ "sessionID",
)
assert_matches_type(PermissionListResponse, permission, path=["response"])
@parametrize
async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
response = await async_client.permission.with_raw_response.list(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -314,7 +314,7 @@ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
async with async_client.permission.with_streaming_response.list(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -334,16 +334,16 @@ async def test_path_params_list(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_get(self, async_client: AsyncOpencode) -> None:
permission = await async_client.permission.get(
- request_id={},
- session_id={},
+ request_id="requestID",
+ session_id="sessionID",
)
assert_matches_type(PermissionRequest, permission, path=["response"])
@parametrize
async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
response = await async_client.permission.with_raw_response.get(
- request_id={},
- session_id={},
+ request_id="requestID",
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -354,8 +354,8 @@ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
async with async_client.permission.with_streaming_response.get(
- request_id={},
- session_id={},
+ request_id="requestID",
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -369,31 +369,31 @@ async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None
async def test_path_params_get(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
await async_client.permission.with_raw_response.get(
- request_id={},
+ request_id="requestID",
session_id="",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `request_id` but received ''"):
await async_client.permission.with_raw_response.get(
request_id="",
- session_id={},
+ session_id="sessionID",
)
@parametrize
async def test_method_reply(self, async_client: AsyncOpencode) -> None:
permission = await async_client.permission.reply(
- request_id={},
- session_id={},
- decision={},
+ request_id="requestID",
+ session_id="sessionID",
+ decision="once",
)
assert permission is None
@parametrize
async def test_method_reply_with_all_params(self, async_client: AsyncOpencode) -> None:
permission = await async_client.permission.reply(
- request_id={},
- session_id={},
- decision={},
+ request_id="requestID",
+ session_id="sessionID",
+ decision="once",
message="message",
)
assert permission is None
@@ -401,9 +401,9 @@ async def test_method_reply_with_all_params(self, async_client: AsyncOpencode) -
@parametrize
async def test_raw_response_reply(self, async_client: AsyncOpencode) -> None:
response = await async_client.permission.with_raw_response.reply(
- request_id={},
- session_id={},
- decision={},
+ request_id="requestID",
+ session_id="sessionID",
+ decision="once",
)
assert response.is_closed is True
@@ -414,9 +414,9 @@ async def test_raw_response_reply(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_reply(self, async_client: AsyncOpencode) -> None:
async with async_client.permission.with_streaming_response.reply(
- request_id={},
- session_id={},
- decision={},
+ request_id="requestID",
+ session_id="sessionID",
+ decision="once",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -430,14 +430,14 @@ async def test_streaming_response_reply(self, async_client: AsyncOpencode) -> No
async def test_path_params_reply(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
await async_client.permission.with_raw_response.reply(
- request_id={},
+ request_id="requestID",
session_id="",
- decision={},
+ decision="once",
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `request_id` but received ''"):
await async_client.permission.with_raw_response.reply(
request_id="",
- session_id={},
- decision={},
+ session_id="sessionID",
+ decision="once",
)
diff --git a/tests/api_resources/test_project.py b/tests/api_resources/test_project.py
index ad78721..36e5917 100644
--- a/tests/api_resources/test_project.py
+++ b/tests/api_resources/test_project.py
@@ -21,15 +21,15 @@ class TestProject:
@parametrize
def test_method_update(self, client: Opencode) -> None:
project = client.project.update(
- project_id={},
+ project_id="projectID",
)
assert_matches_type(Project, project, path=["response"])
@parametrize
def test_method_update_with_all_params(self, client: Opencode) -> None:
project = client.project.update(
- project_id={},
- canonical={},
+ project_id="projectID",
+ canonical="canonical",
commands={},
icon={},
name="name",
@@ -39,7 +39,7 @@ def test_method_update_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_update(self, client: Opencode) -> None:
response = client.project.with_raw_response.update(
- project_id={},
+ project_id="projectID",
)
assert response.is_closed is True
@@ -50,7 +50,7 @@ def test_raw_response_update(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_update(self, client: Opencode) -> None:
with client.project.with_streaming_response.update(
- project_id={},
+ project_id="projectID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -108,15 +108,15 @@ class TestAsyncProject:
@parametrize
async def test_method_update(self, async_client: AsyncOpencode) -> None:
project = await async_client.project.update(
- project_id={},
+ project_id="projectID",
)
assert_matches_type(Project, project, path=["response"])
@parametrize
async def test_method_update_with_all_params(self, async_client: AsyncOpencode) -> None:
project = await async_client.project.update(
- project_id={},
- canonical={},
+ project_id="projectID",
+ canonical="canonical",
commands={},
icon={},
name="name",
@@ -126,7 +126,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
response = await async_client.project.with_raw_response.update(
- project_id={},
+ project_id="projectID",
)
assert response.is_closed is True
@@ -137,7 +137,7 @@ async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_update(self, async_client: AsyncOpencode) -> None:
async with async_client.project.with_streaming_response.update(
- project_id={},
+ project_id="projectID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/test_provider.py b/tests/api_resources/test_provider.py
index 9a0ef40..e16c0b4 100644
--- a/tests/api_resources/test_provider.py
+++ b/tests/api_resources/test_provider.py
@@ -54,14 +54,14 @@ def test_streaming_response_list(self, client: Opencode) -> None:
@parametrize
def test_method_get(self, client: Opencode) -> None:
provider = client.provider.get(
- provider_id={},
+ provider_id="providerID",
)
assert_matches_type(ProviderGetResponse, provider, path=["response"])
@parametrize
def test_method_get_with_all_params(self, client: Opencode) -> None:
provider = client.provider.get(
- provider_id={},
+ provider_id="providerID",
location={
"directory": "string",
},
@@ -71,7 +71,7 @@ def test_method_get_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_get(self, client: Opencode) -> None:
response = client.provider.with_raw_response.get(
- provider_id={},
+ provider_id="providerID",
)
assert response.is_closed is True
@@ -82,7 +82,7 @@ def test_raw_response_get(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_get(self, client: Opencode) -> None:
with client.provider.with_streaming_response.get(
- provider_id={},
+ provider_id="providerID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -142,14 +142,14 @@ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> Non
@parametrize
async def test_method_get(self, async_client: AsyncOpencode) -> None:
provider = await async_client.provider.get(
- provider_id={},
+ provider_id="providerID",
)
assert_matches_type(ProviderGetResponse, provider, path=["response"])
@parametrize
async def test_method_get_with_all_params(self, async_client: AsyncOpencode) -> None:
provider = await async_client.provider.get(
- provider_id={},
+ provider_id="providerID",
location={
"directory": "string",
},
@@ -159,7 +159,7 @@ async def test_method_get_with_all_params(self, async_client: AsyncOpencode) ->
@parametrize
async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
response = await async_client.provider.with_raw_response.get(
- provider_id={},
+ provider_id="providerID",
)
assert response.is_closed is True
@@ -170,7 +170,7 @@ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
async with async_client.provider.with_streaming_response.get(
- provider_id={},
+ provider_id="providerID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/test_pty.py b/tests/api_resources/test_pty.py
index f869d19..8e79b8c 100644
--- a/tests/api_resources/test_pty.py
+++ b/tests/api_resources/test_pty.py
@@ -64,14 +64,14 @@ def test_streaming_response_create(self, client: Opencode) -> None:
@parametrize
def test_method_update(self, client: Opencode) -> None:
pty = client.pty.update(
- pty_id={},
+ pty_id="ptyID",
)
assert_matches_type(PtyUpdateResponse, pty, path=["response"])
@parametrize
def test_method_update_with_all_params(self, client: Opencode) -> None:
pty = client.pty.update(
- pty_id={},
+ pty_id="ptyID",
location={
"directory": "string",
},
@@ -86,7 +86,7 @@ def test_method_update_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_update(self, client: Opencode) -> None:
response = client.pty.with_raw_response.update(
- pty_id={},
+ pty_id="ptyID",
)
assert response.is_closed is True
@@ -97,7 +97,7 @@ def test_raw_response_update(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_update(self, client: Opencode) -> None:
with client.pty.with_streaming_response.update(
- pty_id={},
+ pty_id="ptyID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -151,14 +151,14 @@ def test_streaming_response_list(self, client: Opencode) -> None:
@parametrize
def test_method_get(self, client: Opencode) -> None:
pty = client.pty.get(
- pty_id={},
+ pty_id="ptyID",
)
assert_matches_type(PtyGetResponse, pty, path=["response"])
@parametrize
def test_method_get_with_all_params(self, client: Opencode) -> None:
pty = client.pty.get(
- pty_id={},
+ pty_id="ptyID",
location={
"directory": "string",
},
@@ -168,7 +168,7 @@ def test_method_get_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_get(self, client: Opencode) -> None:
response = client.pty.with_raw_response.get(
- pty_id={},
+ pty_id="ptyID",
)
assert response.is_closed is True
@@ -179,7 +179,7 @@ def test_raw_response_get(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_get(self, client: Opencode) -> None:
with client.pty.with_streaming_response.get(
- pty_id={},
+ pty_id="ptyID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -199,14 +199,14 @@ def test_path_params_get(self, client: Opencode) -> None:
@parametrize
def test_method_remove(self, client: Opencode) -> None:
pty = client.pty.remove(
- pty_id={},
+ pty_id="ptyID",
)
assert pty is None
@parametrize
def test_method_remove_with_all_params(self, client: Opencode) -> None:
pty = client.pty.remove(
- pty_id={},
+ pty_id="ptyID",
location={
"directory": "string",
},
@@ -216,7 +216,7 @@ def test_method_remove_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_remove(self, client: Opencode) -> None:
response = client.pty.with_raw_response.remove(
- pty_id={},
+ pty_id="ptyID",
)
assert response.is_closed is True
@@ -227,7 +227,7 @@ def test_raw_response_remove(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_remove(self, client: Opencode) -> None:
with client.pty.with_streaming_response.remove(
- pty_id={},
+ pty_id="ptyID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -292,14 +292,14 @@ async def test_streaming_response_create(self, async_client: AsyncOpencode) -> N
@parametrize
async def test_method_update(self, async_client: AsyncOpencode) -> None:
pty = await async_client.pty.update(
- pty_id={},
+ pty_id="ptyID",
)
assert_matches_type(PtyUpdateResponse, pty, path=["response"])
@parametrize
async def test_method_update_with_all_params(self, async_client: AsyncOpencode) -> None:
pty = await async_client.pty.update(
- pty_id={},
+ pty_id="ptyID",
location={
"directory": "string",
},
@@ -314,7 +314,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
response = await async_client.pty.with_raw_response.update(
- pty_id={},
+ pty_id="ptyID",
)
assert response.is_closed is True
@@ -325,7 +325,7 @@ async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_update(self, async_client: AsyncOpencode) -> None:
async with async_client.pty.with_streaming_response.update(
- pty_id={},
+ pty_id="ptyID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -379,14 +379,14 @@ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> Non
@parametrize
async def test_method_get(self, async_client: AsyncOpencode) -> None:
pty = await async_client.pty.get(
- pty_id={},
+ pty_id="ptyID",
)
assert_matches_type(PtyGetResponse, pty, path=["response"])
@parametrize
async def test_method_get_with_all_params(self, async_client: AsyncOpencode) -> None:
pty = await async_client.pty.get(
- pty_id={},
+ pty_id="ptyID",
location={
"directory": "string",
},
@@ -396,7 +396,7 @@ async def test_method_get_with_all_params(self, async_client: AsyncOpencode) ->
@parametrize
async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
response = await async_client.pty.with_raw_response.get(
- pty_id={},
+ pty_id="ptyID",
)
assert response.is_closed is True
@@ -407,7 +407,7 @@ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
async with async_client.pty.with_streaming_response.get(
- pty_id={},
+ pty_id="ptyID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -427,14 +427,14 @@ async def test_path_params_get(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_remove(self, async_client: AsyncOpencode) -> None:
pty = await async_client.pty.remove(
- pty_id={},
+ pty_id="ptyID",
)
assert pty is None
@parametrize
async def test_method_remove_with_all_params(self, async_client: AsyncOpencode) -> None:
pty = await async_client.pty.remove(
- pty_id={},
+ pty_id="ptyID",
location={
"directory": "string",
},
@@ -444,7 +444,7 @@ async def test_method_remove_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
response = await async_client.pty.with_raw_response.remove(
- pty_id={},
+ pty_id="ptyID",
)
assert response.is_closed is True
@@ -455,7 +455,7 @@ async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_remove(self, async_client: AsyncOpencode) -> None:
async with async_client.pty.with_streaming_response.remove(
- pty_id={},
+ pty_id="ptyID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/test_session.py b/tests/api_resources/test_session.py
index 4878d39..68e1c95 100644
--- a/tests/api_resources/test_session.py
+++ b/tests/api_resources/test_session.py
@@ -73,14 +73,14 @@ def test_streaming_response_create(self, client: Opencode) -> None:
@parametrize
def test_method_update(self, client: Opencode) -> None:
session = client.session.update(
- session_id={},
+ session_id="sessionID",
)
assert session is None
@parametrize
def test_method_update_with_all_params(self, client: Opencode) -> None:
session = client.session.update(
- session_id={},
+ session_id="sessionID",
archived=True,
metadata={},
permissions={},
@@ -91,7 +91,7 @@ def test_method_update_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_update(self, client: Opencode) -> None:
response = client.session.with_raw_response.update(
- session_id={},
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -102,7 +102,7 @@ def test_raw_response_update(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_update(self, client: Opencode) -> None:
with client.session.with_streaming_response.update(
- session_id={},
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -187,14 +187,14 @@ def test_streaming_response_active(self, client: Opencode) -> None:
@parametrize
def test_method_background(self, client: Opencode) -> None:
session = client.session.background(
- {},
+ "sessionID",
)
assert session is None
@parametrize
def test_raw_response_background(self, client: Opencode) -> None:
response = client.session.with_raw_response.background(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -205,7 +205,7 @@ def test_raw_response_background(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_background(self, client: Opencode) -> None:
with client.session.with_streaming_response.background(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -225,7 +225,7 @@ def test_path_params_background(self, client: Opencode) -> None:
@parametrize
def test_method_command(self, client: Opencode) -> None:
session = client.session.command(
- session_id={},
+ session_id="sessionID",
name="name",
text="text",
)
@@ -234,7 +234,7 @@ def test_method_command(self, client: Opencode) -> None:
@parametrize
def test_method_command_with_all_params(self, client: Opencode) -> None:
session = client.session.command(
- session_id={},
+ session_id="sessionID",
name="name",
text="text",
agents=[{}],
@@ -247,7 +247,7 @@ def test_method_command_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_command(self, client: Opencode) -> None:
response = client.session.with_raw_response.command(
- session_id={},
+ session_id="sessionID",
name="name",
text="text",
)
@@ -260,7 +260,7 @@ def test_raw_response_command(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_command(self, client: Opencode) -> None:
with client.session.with_streaming_response.command(
- session_id={},
+ session_id="sessionID",
name="name",
text="text",
) as response:
@@ -284,14 +284,14 @@ def test_path_params_command(self, client: Opencode) -> None:
@parametrize
def test_method_compact(self, client: Opencode) -> None:
session = client.session.compact(
- session_id={},
+ session_id="sessionID",
)
assert_matches_type(SessionInboxCompaction, session, path=["response"])
@parametrize
def test_method_compact_with_all_params(self, client: Opencode) -> None:
session = client.session.compact(
- session_id={},
+ session_id="sessionID",
delivery={},
id={},
)
@@ -300,7 +300,7 @@ def test_method_compact_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_compact(self, client: Opencode) -> None:
response = client.session.with_raw_response.compact(
- session_id={},
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -311,7 +311,7 @@ def test_raw_response_compact(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_compact(self, client: Opencode) -> None:
with client.session.with_streaming_response.compact(
- session_id={},
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -331,14 +331,14 @@ def test_path_params_compact(self, client: Opencode) -> None:
@parametrize
def test_method_context(self, client: Opencode) -> None:
session = client.session.context(
- {},
+ "sessionID",
)
assert_matches_type(SessionContextResponse, session, path=["response"])
@parametrize
def test_raw_response_context(self, client: Opencode) -> None:
response = client.session.with_raw_response.context(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -349,7 +349,7 @@ def test_raw_response_context(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_context(self, client: Opencode) -> None:
with client.session.with_streaming_response.context(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -369,14 +369,14 @@ def test_path_params_context(self, client: Opencode) -> None:
@parametrize
def test_method_diff(self, client: Opencode) -> None:
session = client.session.diff(
- session_id={},
+ session_id="sessionID",
)
assert_matches_type(SessionDiffResponse, session, path=["response"])
@parametrize
def test_method_diff_with_all_params(self, client: Opencode) -> None:
session = client.session.diff(
- session_id={},
+ session_id="sessionID",
context=0,
from_={},
to={},
@@ -386,7 +386,7 @@ def test_method_diff_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_diff(self, client: Opencode) -> None:
response = client.session.with_raw_response.diff(
- session_id={},
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -397,7 +397,7 @@ def test_raw_response_diff(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_diff(self, client: Opencode) -> None:
with client.session.with_streaming_response.diff(
- session_id={},
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -417,7 +417,7 @@ def test_path_params_diff(self, client: Opencode) -> None:
@parametrize
def test_method_environment(self, client: Opencode) -> None:
session = client.session.environment(
- session_id={},
+ session_id="sessionID",
variables={"foo": "string"},
)
assert session is None
@@ -425,7 +425,7 @@ def test_method_environment(self, client: Opencode) -> None:
@parametrize
def test_raw_response_environment(self, client: Opencode) -> None:
response = client.session.with_raw_response.environment(
- session_id={},
+ session_id="sessionID",
variables={"foo": "string"},
)
@@ -437,7 +437,7 @@ def test_raw_response_environment(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_environment(self, client: Opencode) -> None:
with client.session.with_streaming_response.environment(
- session_id={},
+ session_id="sessionID",
variables={"foo": "string"},
) as response:
assert not response.is_closed
@@ -459,14 +459,14 @@ def test_path_params_environment(self, client: Opencode) -> None:
@parametrize
def test_method_export(self, client: Opencode) -> None:
session = client.session.export(
- session_id={},
+ session_id="sessionID",
)
assert_matches_type(SessionTransferData, session, path=["response"])
@parametrize
def test_method_export_with_all_params(self, client: Opencode) -> None:
session = client.session.export(
- session_id={},
+ session_id="sessionID",
sanitize=True,
)
assert_matches_type(SessionTransferData, session, path=["response"])
@@ -474,7 +474,7 @@ def test_method_export_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_export(self, client: Opencode) -> None:
response = client.session.with_raw_response.export(
- session_id={},
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -485,7 +485,7 @@ def test_raw_response_export(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_export(self, client: Opencode) -> None:
with client.session.with_streaming_response.export(
- session_id={},
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -505,14 +505,14 @@ def test_path_params_export(self, client: Opencode) -> None:
@parametrize
def test_method_fork(self, client: Opencode) -> None:
session = client.session.fork(
- session_id={},
+ session_id="sessionID",
)
assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
def test_method_fork_with_all_params(self, client: Opencode) -> None:
session = client.session.fork(
- session_id={},
+ session_id="sessionID",
before={},
)
assert_matches_type(SessionInfo, session, path=["response"])
@@ -520,7 +520,7 @@ def test_method_fork_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_fork(self, client: Opencode) -> None:
response = client.session.with_raw_response.fork(
- session_id={},
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -531,7 +531,7 @@ def test_raw_response_fork(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_fork(self, client: Opencode) -> None:
with client.session.with_streaming_response.fork(
- session_id={},
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -551,7 +551,7 @@ def test_path_params_fork(self, client: Opencode) -> None:
@parametrize
def test_method_generate(self, client: Opencode) -> None:
session = client.session.generate(
- session_id={},
+ session_id="sessionID",
prompt="prompt",
)
assert_matches_type(SessionGenerateResponse, session, path=["response"])
@@ -559,7 +559,7 @@ def test_method_generate(self, client: Opencode) -> None:
@parametrize
def test_raw_response_generate(self, client: Opencode) -> None:
response = client.session.with_raw_response.generate(
- session_id={},
+ session_id="sessionID",
prompt="prompt",
)
@@ -571,7 +571,7 @@ def test_raw_response_generate(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_generate(self, client: Opencode) -> None:
with client.session.with_streaming_response.generate(
- session_id={},
+ session_id="sessionID",
prompt="prompt",
) as response:
assert not response.is_closed
@@ -593,14 +593,14 @@ def test_path_params_generate(self, client: Opencode) -> None:
@parametrize
def test_method_get(self, client: Opencode) -> None:
session = client.session.get(
- {},
+ "sessionID",
)
assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
def test_raw_response_get(self, client: Opencode) -> None:
response = client.session.with_raw_response.get(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -611,7 +611,7 @@ def test_raw_response_get(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_get(self, client: Opencode) -> None:
with client.session.with_streaming_response.get(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -674,14 +674,14 @@ def test_streaming_response_import_(self, client: Opencode) -> None:
@parametrize
def test_method_interrupt(self, client: Opencode) -> None:
session = client.session.interrupt(
- session_id={},
+ session_id="sessionID",
)
assert_matches_type(SessionInterruptResponse, session, path=["response"])
@parametrize
def test_method_interrupt_with_all_params(self, client: Opencode) -> None:
session = client.session.interrupt(
- session_id={},
+ session_id="sessionID",
resume=True,
)
assert_matches_type(SessionInterruptResponse, session, path=["response"])
@@ -689,7 +689,7 @@ def test_method_interrupt_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_interrupt(self, client: Opencode) -> None:
response = client.session.with_raw_response.interrupt(
- session_id={},
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -700,7 +700,7 @@ def test_raw_response_interrupt(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_interrupt(self, client: Opencode) -> None:
with client.session.with_streaming_response.interrupt(
- session_id={},
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -720,14 +720,14 @@ def test_path_params_interrupt(self, client: Opencode) -> None:
@parametrize
def test_method_log(self, client: Opencode) -> None:
session = client.session.log(
- session_id={},
+ session_id="sessionID",
)
assert session is None
@parametrize
def test_method_log_with_all_params(self, client: Opencode) -> None:
session = client.session.log(
- session_id={},
+ session_id="sessionID",
after={},
follow=True,
)
@@ -736,7 +736,7 @@ def test_method_log_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_log(self, client: Opencode) -> None:
response = client.session.with_raw_response.log(
- session_id={},
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -747,7 +747,7 @@ def test_raw_response_log(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_log(self, client: Opencode) -> None:
with client.session.with_streaming_response.log(
- session_id={},
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -767,16 +767,16 @@ def test_path_params_log(self, client: Opencode) -> None:
@parametrize
def test_method_move(self, client: Opencode) -> None:
session = client.session.move(
- session_id={},
- directory={},
+ session_id="sessionID",
+ directory="directory",
)
assert session is None
@parametrize
def test_method_move_with_all_params(self, client: Opencode) -> None:
session = client.session.move(
- session_id={},
- directory={},
+ session_id="sessionID",
+ directory="directory",
delivery={},
)
assert session is None
@@ -784,8 +784,8 @@ def test_method_move_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_move(self, client: Opencode) -> None:
response = client.session.with_raw_response.move(
- session_id={},
- directory={},
+ session_id="sessionID",
+ directory="directory",
)
assert response.is_closed is True
@@ -796,8 +796,8 @@ def test_raw_response_move(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_move(self, client: Opencode) -> None:
with client.session.with_streaming_response.move(
- session_id={},
- directory={},
+ session_id="sessionID",
+ directory="directory",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -812,13 +812,13 @@ def test_path_params_move(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
client.session.with_raw_response.move(
session_id="",
- directory={},
+ directory="directory",
)
@parametrize
def test_method_prompt(self, client: Opencode) -> None:
session = client.session.prompt(
- session_id={},
+ session_id="sessionID",
text="text",
)
assert_matches_type(SessionInboxUser, session, path=["response"])
@@ -826,7 +826,7 @@ def test_method_prompt(self, client: Opencode) -> None:
@parametrize
def test_method_prompt_with_all_params(self, client: Opencode) -> None:
session = client.session.prompt(
- session_id={},
+ session_id="sessionID",
text="text",
agents=[{}],
delivery={},
@@ -841,7 +841,7 @@ def test_method_prompt_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_prompt(self, client: Opencode) -> None:
response = client.session.with_raw_response.prompt(
- session_id={},
+ session_id="sessionID",
text="text",
)
@@ -853,7 +853,7 @@ def test_raw_response_prompt(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_prompt(self, client: Opencode) -> None:
with client.session.with_streaming_response.prompt(
- session_id={},
+ session_id="sessionID",
text="text",
) as response:
assert not response.is_closed
@@ -875,14 +875,14 @@ def test_path_params_prompt(self, client: Opencode) -> None:
@parametrize
def test_method_remove(self, client: Opencode) -> None:
session = client.session.remove(
- {},
+ "sessionID",
)
assert session is None
@parametrize
def test_raw_response_remove(self, client: Opencode) -> None:
response = client.session.with_raw_response.remove(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -893,7 +893,7 @@ def test_raw_response_remove(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_remove(self, client: Opencode) -> None:
with client.session.with_streaming_response.remove(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -913,7 +913,7 @@ def test_path_params_remove(self, client: Opencode) -> None:
@parametrize
def test_method_shell(self, client: Opencode) -> None:
session = client.session.shell(
- session_id={},
+ session_id="sessionID",
command="command",
)
assert session is None
@@ -921,7 +921,7 @@ def test_method_shell(self, client: Opencode) -> None:
@parametrize
def test_method_shell_with_all_params(self, client: Opencode) -> None:
session = client.session.shell(
- session_id={},
+ session_id="sessionID",
command="command",
id={},
)
@@ -930,7 +930,7 @@ def test_method_shell_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_shell(self, client: Opencode) -> None:
response = client.session.with_raw_response.shell(
- session_id={},
+ session_id="sessionID",
command="command",
)
@@ -942,7 +942,7 @@ def test_raw_response_shell(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_shell(self, client: Opencode) -> None:
with client.session.with_streaming_response.shell(
- session_id={},
+ session_id="sessionID",
command="command",
) as response:
assert not response.is_closed
@@ -964,16 +964,16 @@ def test_path_params_shell(self, client: Opencode) -> None:
@parametrize
def test_method_skill(self, client: Opencode) -> None:
session = client.session.skill(
- session_id={},
- id={},
+ session_id="sessionID",
+ id="id",
)
assert session is None
@parametrize
def test_method_skill_with_all_params(self, client: Opencode) -> None:
session = client.session.skill(
- session_id={},
- id={},
+ session_id="sessionID",
+ id="id",
resume=True,
)
assert session is None
@@ -981,8 +981,8 @@ def test_method_skill_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_skill(self, client: Opencode) -> None:
response = client.session.with_raw_response.skill(
- session_id={},
- id={},
+ session_id="sessionID",
+ id="id",
)
assert response.is_closed is True
@@ -993,8 +993,8 @@ def test_raw_response_skill(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_skill(self, client: Opencode) -> None:
with client.session.with_streaming_response.skill(
- session_id={},
- id={},
+ session_id="sessionID",
+ id="id",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1009,7 +1009,7 @@ def test_path_params_skill(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
client.session.with_raw_response.skill(
session_id="",
- id={},
+ id="id",
)
@parametrize
@@ -1051,16 +1051,16 @@ def test_streaming_response_stats(self, client: Opencode) -> None:
@parametrize
def test_method_switch_agent(self, client: Opencode) -> None:
session = client.session.switch_agent(
- session_id={},
- agent={},
+ session_id="sessionID",
+ agent="agent",
)
assert session is None
@parametrize
def test_raw_response_switch_agent(self, client: Opencode) -> None:
response = client.session.with_raw_response.switch_agent(
- session_id={},
- agent={},
+ session_id="sessionID",
+ agent="agent",
)
assert response.is_closed is True
@@ -1071,8 +1071,8 @@ def test_raw_response_switch_agent(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_switch_agent(self, client: Opencode) -> None:
with client.session.with_streaming_response.switch_agent(
- session_id={},
- agent={},
+ session_id="sessionID",
+ agent="agent",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1087,13 +1087,13 @@ def test_path_params_switch_agent(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
client.session.with_raw_response.switch_agent(
session_id="",
- agent={},
+ agent="agent",
)
@parametrize
def test_method_switch_model(self, client: Opencode) -> None:
session = client.session.switch_model(
- session_id={},
+ session_id="sessionID",
model={},
)
assert session is None
@@ -1101,7 +1101,7 @@ def test_method_switch_model(self, client: Opencode) -> None:
@parametrize
def test_raw_response_switch_model(self, client: Opencode) -> None:
response = client.session.with_raw_response.switch_model(
- session_id={},
+ session_id="sessionID",
model={},
)
@@ -1113,7 +1113,7 @@ def test_raw_response_switch_model(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_switch_model(self, client: Opencode) -> None:
with client.session.with_streaming_response.switch_model(
- session_id={},
+ session_id="sessionID",
model={},
) as response:
assert not response.is_closed
@@ -1135,7 +1135,7 @@ def test_path_params_switch_model(self, client: Opencode) -> None:
@parametrize
def test_method_synthetic(self, client: Opencode) -> None:
session = client.session.synthetic(
- session_id={},
+ session_id="sessionID",
text="text",
)
assert_matches_type(SessionInboxSynthetic, session, path=["response"])
@@ -1143,7 +1143,7 @@ def test_method_synthetic(self, client: Opencode) -> None:
@parametrize
def test_method_synthetic_with_all_params(self, client: Opencode) -> None:
session = client.session.synthetic(
- session_id={},
+ session_id="sessionID",
text="text",
delivery={},
description="description",
@@ -1156,7 +1156,7 @@ def test_method_synthetic_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_synthetic(self, client: Opencode) -> None:
response = client.session.with_raw_response.synthetic(
- session_id={},
+ session_id="sessionID",
text="text",
)
@@ -1168,7 +1168,7 @@ def test_raw_response_synthetic(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_synthetic(self, client: Opencode) -> None:
with client.session.with_streaming_response.synthetic(
- session_id={},
+ session_id="sessionID",
text="text",
) as response:
assert not response.is_closed
@@ -1190,7 +1190,7 @@ def test_path_params_synthetic(self, client: Opencode) -> None:
@parametrize
def test_method_view(self, client: Opencode) -> None:
session = client.session.view(
- session_id={},
+ session_id="sessionID",
idle=0,
)
assert session is None
@@ -1198,7 +1198,7 @@ def test_method_view(self, client: Opencode) -> None:
@parametrize
def test_raw_response_view(self, client: Opencode) -> None:
response = client.session.with_raw_response.view(
- session_id={},
+ session_id="sessionID",
idle=0,
)
@@ -1210,7 +1210,7 @@ def test_raw_response_view(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_view(self, client: Opencode) -> None:
with client.session.with_streaming_response.view(
- session_id={},
+ session_id="sessionID",
idle=0,
) as response:
assert not response.is_closed
@@ -1232,14 +1232,14 @@ def test_path_params_view(self, client: Opencode) -> None:
@parametrize
def test_method_wait(self, client: Opencode) -> None:
session = client.session.wait(
- {},
+ "sessionID",
)
assert session is None
@parametrize
def test_raw_response_wait(self, client: Opencode) -> None:
response = client.session.with_raw_response.wait(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -1250,7 +1250,7 @@ def test_raw_response_wait(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_wait(self, client: Opencode) -> None:
with client.session.with_streaming_response.wait(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1315,14 +1315,14 @@ async def test_streaming_response_create(self, async_client: AsyncOpencode) -> N
@parametrize
async def test_method_update(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.update(
- session_id={},
+ session_id="sessionID",
)
assert session is None
@parametrize
async def test_method_update_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.update(
- session_id={},
+ session_id="sessionID",
archived=True,
metadata={},
permissions={},
@@ -1333,7 +1333,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.update(
- session_id={},
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -1344,7 +1344,7 @@ async def test_raw_response_update(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_update(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.update(
- session_id={},
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1429,14 +1429,14 @@ async def test_streaming_response_active(self, async_client: AsyncOpencode) -> N
@parametrize
async def test_method_background(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.background(
- {},
+ "sessionID",
)
assert session is None
@parametrize
async def test_raw_response_background(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.background(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -1447,7 +1447,7 @@ async def test_raw_response_background(self, async_client: AsyncOpencode) -> Non
@parametrize
async def test_streaming_response_background(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.background(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1467,7 +1467,7 @@ async def test_path_params_background(self, async_client: AsyncOpencode) -> None
@parametrize
async def test_method_command(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.command(
- session_id={},
+ session_id="sessionID",
name="name",
text="text",
)
@@ -1476,7 +1476,7 @@ async def test_method_command(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_command_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.command(
- session_id={},
+ session_id="sessionID",
name="name",
text="text",
agents=[{}],
@@ -1489,7 +1489,7 @@ async def test_method_command_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_command(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.command(
- session_id={},
+ session_id="sessionID",
name="name",
text="text",
)
@@ -1502,7 +1502,7 @@ async def test_raw_response_command(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_command(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.command(
- session_id={},
+ session_id="sessionID",
name="name",
text="text",
) as response:
@@ -1526,14 +1526,14 @@ async def test_path_params_command(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_compact(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.compact(
- session_id={},
+ session_id="sessionID",
)
assert_matches_type(SessionInboxCompaction, session, path=["response"])
@parametrize
async def test_method_compact_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.compact(
- session_id={},
+ session_id="sessionID",
delivery={},
id={},
)
@@ -1542,7 +1542,7 @@ async def test_method_compact_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_compact(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.compact(
- session_id={},
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -1553,7 +1553,7 @@ async def test_raw_response_compact(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_compact(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.compact(
- session_id={},
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1573,14 +1573,14 @@ async def test_path_params_compact(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_context(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.context(
- {},
+ "sessionID",
)
assert_matches_type(SessionContextResponse, session, path=["response"])
@parametrize
async def test_raw_response_context(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.context(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -1591,7 +1591,7 @@ async def test_raw_response_context(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_context(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.context(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1611,14 +1611,14 @@ async def test_path_params_context(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_diff(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.diff(
- session_id={},
+ session_id="sessionID",
)
assert_matches_type(SessionDiffResponse, session, path=["response"])
@parametrize
async def test_method_diff_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.diff(
- session_id={},
+ session_id="sessionID",
context=0,
from_={},
to={},
@@ -1628,7 +1628,7 @@ async def test_method_diff_with_all_params(self, async_client: AsyncOpencode) ->
@parametrize
async def test_raw_response_diff(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.diff(
- session_id={},
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -1639,7 +1639,7 @@ async def test_raw_response_diff(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_diff(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.diff(
- session_id={},
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1659,7 +1659,7 @@ async def test_path_params_diff(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_environment(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.environment(
- session_id={},
+ session_id="sessionID",
variables={"foo": "string"},
)
assert session is None
@@ -1667,7 +1667,7 @@ async def test_method_environment(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_raw_response_environment(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.environment(
- session_id={},
+ session_id="sessionID",
variables={"foo": "string"},
)
@@ -1679,7 +1679,7 @@ async def test_raw_response_environment(self, async_client: AsyncOpencode) -> No
@parametrize
async def test_streaming_response_environment(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.environment(
- session_id={},
+ session_id="sessionID",
variables={"foo": "string"},
) as response:
assert not response.is_closed
@@ -1701,14 +1701,14 @@ async def test_path_params_environment(self, async_client: AsyncOpencode) -> Non
@parametrize
async def test_method_export(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.export(
- session_id={},
+ session_id="sessionID",
)
assert_matches_type(SessionTransferData, session, path=["response"])
@parametrize
async def test_method_export_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.export(
- session_id={},
+ session_id="sessionID",
sanitize=True,
)
assert_matches_type(SessionTransferData, session, path=["response"])
@@ -1716,7 +1716,7 @@ async def test_method_export_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_export(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.export(
- session_id={},
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -1727,7 +1727,7 @@ async def test_raw_response_export(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_export(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.export(
- session_id={},
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1747,14 +1747,14 @@ async def test_path_params_export(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_fork(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.fork(
- session_id={},
+ session_id="sessionID",
)
assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
async def test_method_fork_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.fork(
- session_id={},
+ session_id="sessionID",
before={},
)
assert_matches_type(SessionInfo, session, path=["response"])
@@ -1762,7 +1762,7 @@ async def test_method_fork_with_all_params(self, async_client: AsyncOpencode) ->
@parametrize
async def test_raw_response_fork(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.fork(
- session_id={},
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -1773,7 +1773,7 @@ async def test_raw_response_fork(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_fork(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.fork(
- session_id={},
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1793,7 +1793,7 @@ async def test_path_params_fork(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_generate(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.generate(
- session_id={},
+ session_id="sessionID",
prompt="prompt",
)
assert_matches_type(SessionGenerateResponse, session, path=["response"])
@@ -1801,7 +1801,7 @@ async def test_method_generate(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_raw_response_generate(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.generate(
- session_id={},
+ session_id="sessionID",
prompt="prompt",
)
@@ -1813,7 +1813,7 @@ async def test_raw_response_generate(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_generate(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.generate(
- session_id={},
+ session_id="sessionID",
prompt="prompt",
) as response:
assert not response.is_closed
@@ -1835,14 +1835,14 @@ async def test_path_params_generate(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_get(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.get(
- {},
+ "sessionID",
)
assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.get(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -1853,7 +1853,7 @@ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.get(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1916,14 +1916,14 @@ async def test_streaming_response_import_(self, async_client: AsyncOpencode) ->
@parametrize
async def test_method_interrupt(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.interrupt(
- session_id={},
+ session_id="sessionID",
)
assert_matches_type(SessionInterruptResponse, session, path=["response"])
@parametrize
async def test_method_interrupt_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.interrupt(
- session_id={},
+ session_id="sessionID",
resume=True,
)
assert_matches_type(SessionInterruptResponse, session, path=["response"])
@@ -1931,7 +1931,7 @@ async def test_method_interrupt_with_all_params(self, async_client: AsyncOpencod
@parametrize
async def test_raw_response_interrupt(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.interrupt(
- session_id={},
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -1942,7 +1942,7 @@ async def test_raw_response_interrupt(self, async_client: AsyncOpencode) -> None
@parametrize
async def test_streaming_response_interrupt(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.interrupt(
- session_id={},
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1962,14 +1962,14 @@ async def test_path_params_interrupt(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_log(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.log(
- session_id={},
+ session_id="sessionID",
)
assert session is None
@parametrize
async def test_method_log_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.log(
- session_id={},
+ session_id="sessionID",
after={},
follow=True,
)
@@ -1978,7 +1978,7 @@ async def test_method_log_with_all_params(self, async_client: AsyncOpencode) ->
@parametrize
async def test_raw_response_log(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.log(
- session_id={},
+ session_id="sessionID",
)
assert response.is_closed is True
@@ -1989,7 +1989,7 @@ async def test_raw_response_log(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_log(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.log(
- session_id={},
+ session_id="sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -2009,16 +2009,16 @@ async def test_path_params_log(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_move(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.move(
- session_id={},
- directory={},
+ session_id="sessionID",
+ directory="directory",
)
assert session is None
@parametrize
async def test_method_move_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.move(
- session_id={},
- directory={},
+ session_id="sessionID",
+ directory="directory",
delivery={},
)
assert session is None
@@ -2026,8 +2026,8 @@ async def test_method_move_with_all_params(self, async_client: AsyncOpencode) ->
@parametrize
async def test_raw_response_move(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.move(
- session_id={},
- directory={},
+ session_id="sessionID",
+ directory="directory",
)
assert response.is_closed is True
@@ -2038,8 +2038,8 @@ async def test_raw_response_move(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_move(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.move(
- session_id={},
- directory={},
+ session_id="sessionID",
+ directory="directory",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -2054,13 +2054,13 @@ async def test_path_params_move(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
await async_client.session.with_raw_response.move(
session_id="",
- directory={},
+ directory="directory",
)
@parametrize
async def test_method_prompt(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.prompt(
- session_id={},
+ session_id="sessionID",
text="text",
)
assert_matches_type(SessionInboxUser, session, path=["response"])
@@ -2068,7 +2068,7 @@ async def test_method_prompt(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_prompt_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.prompt(
- session_id={},
+ session_id="sessionID",
text="text",
agents=[{}],
delivery={},
@@ -2083,7 +2083,7 @@ async def test_method_prompt_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_prompt(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.prompt(
- session_id={},
+ session_id="sessionID",
text="text",
)
@@ -2095,7 +2095,7 @@ async def test_raw_response_prompt(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_prompt(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.prompt(
- session_id={},
+ session_id="sessionID",
text="text",
) as response:
assert not response.is_closed
@@ -2117,14 +2117,14 @@ async def test_path_params_prompt(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_remove(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.remove(
- {},
+ "sessionID",
)
assert session is None
@parametrize
async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.remove(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -2135,7 +2135,7 @@ async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_remove(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.remove(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -2155,7 +2155,7 @@ async def test_path_params_remove(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_shell(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.shell(
- session_id={},
+ session_id="sessionID",
command="command",
)
assert session is None
@@ -2163,7 +2163,7 @@ async def test_method_shell(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_shell_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.shell(
- session_id={},
+ session_id="sessionID",
command="command",
id={},
)
@@ -2172,7 +2172,7 @@ async def test_method_shell_with_all_params(self, async_client: AsyncOpencode) -
@parametrize
async def test_raw_response_shell(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.shell(
- session_id={},
+ session_id="sessionID",
command="command",
)
@@ -2184,7 +2184,7 @@ async def test_raw_response_shell(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_shell(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.shell(
- session_id={},
+ session_id="sessionID",
command="command",
) as response:
assert not response.is_closed
@@ -2206,16 +2206,16 @@ async def test_path_params_shell(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_skill(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.skill(
- session_id={},
- id={},
+ session_id="sessionID",
+ id="id",
)
assert session is None
@parametrize
async def test_method_skill_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.skill(
- session_id={},
- id={},
+ session_id="sessionID",
+ id="id",
resume=True,
)
assert session is None
@@ -2223,8 +2223,8 @@ async def test_method_skill_with_all_params(self, async_client: AsyncOpencode) -
@parametrize
async def test_raw_response_skill(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.skill(
- session_id={},
- id={},
+ session_id="sessionID",
+ id="id",
)
assert response.is_closed is True
@@ -2235,8 +2235,8 @@ async def test_raw_response_skill(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_skill(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.skill(
- session_id={},
- id={},
+ session_id="sessionID",
+ id="id",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -2251,7 +2251,7 @@ async def test_path_params_skill(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
await async_client.session.with_raw_response.skill(
session_id="",
- id={},
+ id="id",
)
@parametrize
@@ -2293,16 +2293,16 @@ async def test_streaming_response_stats(self, async_client: AsyncOpencode) -> No
@parametrize
async def test_method_switch_agent(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.switch_agent(
- session_id={},
- agent={},
+ session_id="sessionID",
+ agent="agent",
)
assert session is None
@parametrize
async def test_raw_response_switch_agent(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.switch_agent(
- session_id={},
- agent={},
+ session_id="sessionID",
+ agent="agent",
)
assert response.is_closed is True
@@ -2313,8 +2313,8 @@ async def test_raw_response_switch_agent(self, async_client: AsyncOpencode) -> N
@parametrize
async def test_streaming_response_switch_agent(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.switch_agent(
- session_id={},
- agent={},
+ session_id="sessionID",
+ agent="agent",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -2329,13 +2329,13 @@ async def test_path_params_switch_agent(self, async_client: AsyncOpencode) -> No
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
await async_client.session.with_raw_response.switch_agent(
session_id="",
- agent={},
+ agent="agent",
)
@parametrize
async def test_method_switch_model(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.switch_model(
- session_id={},
+ session_id="sessionID",
model={},
)
assert session is None
@@ -2343,7 +2343,7 @@ async def test_method_switch_model(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_raw_response_switch_model(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.switch_model(
- session_id={},
+ session_id="sessionID",
model={},
)
@@ -2355,7 +2355,7 @@ async def test_raw_response_switch_model(self, async_client: AsyncOpencode) -> N
@parametrize
async def test_streaming_response_switch_model(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.switch_model(
- session_id={},
+ session_id="sessionID",
model={},
) as response:
assert not response.is_closed
@@ -2377,7 +2377,7 @@ async def test_path_params_switch_model(self, async_client: AsyncOpencode) -> No
@parametrize
async def test_method_synthetic(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.synthetic(
- session_id={},
+ session_id="sessionID",
text="text",
)
assert_matches_type(SessionInboxSynthetic, session, path=["response"])
@@ -2385,7 +2385,7 @@ async def test_method_synthetic(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_synthetic_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.synthetic(
- session_id={},
+ session_id="sessionID",
text="text",
delivery={},
description="description",
@@ -2398,7 +2398,7 @@ async def test_method_synthetic_with_all_params(self, async_client: AsyncOpencod
@parametrize
async def test_raw_response_synthetic(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.synthetic(
- session_id={},
+ session_id="sessionID",
text="text",
)
@@ -2410,7 +2410,7 @@ async def test_raw_response_synthetic(self, async_client: AsyncOpencode) -> None
@parametrize
async def test_streaming_response_synthetic(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.synthetic(
- session_id={},
+ session_id="sessionID",
text="text",
) as response:
assert not response.is_closed
@@ -2432,7 +2432,7 @@ async def test_path_params_synthetic(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_view(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.view(
- session_id={},
+ session_id="sessionID",
idle=0,
)
assert session is None
@@ -2440,7 +2440,7 @@ async def test_method_view(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_raw_response_view(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.view(
- session_id={},
+ session_id="sessionID",
idle=0,
)
@@ -2452,7 +2452,7 @@ async def test_raw_response_view(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_view(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.view(
- session_id={},
+ session_id="sessionID",
idle=0,
) as response:
assert not response.is_closed
@@ -2474,14 +2474,14 @@ async def test_path_params_view(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_wait(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.wait(
- {},
+ "sessionID",
)
assert session is None
@parametrize
async def test_raw_response_wait(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.wait(
- {},
+ "sessionID",
)
assert response.is_closed is True
@@ -2492,7 +2492,7 @@ async def test_raw_response_wait(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_wait(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.wait(
- {},
+ "sessionID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/test_shell.py b/tests/api_resources/test_shell.py
index c8efba8..98704c0 100644
--- a/tests/api_resources/test_shell.py
+++ b/tests/api_resources/test_shell.py
@@ -103,14 +103,14 @@ def test_streaming_response_list(self, client: Opencode) -> None:
@parametrize
def test_method_get(self, client: Opencode) -> None:
shell = client.shell.get(
- id={},
+ id="id",
)
assert_matches_type(ShellGetResponse, shell, path=["response"])
@parametrize
def test_method_get_with_all_params(self, client: Opencode) -> None:
shell = client.shell.get(
- id={},
+ id="id",
location={
"directory": "string",
},
@@ -120,7 +120,7 @@ def test_method_get_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_get(self, client: Opencode) -> None:
response = client.shell.with_raw_response.get(
- id={},
+ id="id",
)
assert response.is_closed is True
@@ -131,7 +131,7 @@ def test_raw_response_get(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_get(self, client: Opencode) -> None:
with client.shell.with_streaming_response.get(
- id={},
+ id="id",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -151,14 +151,14 @@ def test_path_params_get(self, client: Opencode) -> None:
@parametrize
def test_method_output(self, client: Opencode) -> None:
shell = client.shell.output(
- id={},
+ id="id",
)
assert_matches_type(ShellOutputResponse, shell, path=["response"])
@parametrize
def test_method_output_with_all_params(self, client: Opencode) -> None:
shell = client.shell.output(
- id={},
+ id="id",
cursor=0,
limit=0,
location={
@@ -170,7 +170,7 @@ def test_method_output_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_output(self, client: Opencode) -> None:
response = client.shell.with_raw_response.output(
- id={},
+ id="id",
)
assert response.is_closed is True
@@ -181,7 +181,7 @@ def test_raw_response_output(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_output(self, client: Opencode) -> None:
with client.shell.with_streaming_response.output(
- id={},
+ id="id",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -201,14 +201,14 @@ def test_path_params_output(self, client: Opencode) -> None:
@parametrize
def test_method_remove(self, client: Opencode) -> None:
shell = client.shell.remove(
- id={},
+ id="id",
)
assert shell is None
@parametrize
def test_method_remove_with_all_params(self, client: Opencode) -> None:
shell = client.shell.remove(
- id={},
+ id="id",
location={
"directory": "string",
},
@@ -218,7 +218,7 @@ def test_method_remove_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_remove(self, client: Opencode) -> None:
response = client.shell.with_raw_response.remove(
- id={},
+ id="id",
)
assert response.is_closed is True
@@ -229,7 +229,7 @@ def test_raw_response_remove(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_remove(self, client: Opencode) -> None:
with client.shell.with_streaming_response.remove(
- id={},
+ id="id",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -333,14 +333,14 @@ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> Non
@parametrize
async def test_method_get(self, async_client: AsyncOpencode) -> None:
shell = await async_client.shell.get(
- id={},
+ id="id",
)
assert_matches_type(ShellGetResponse, shell, path=["response"])
@parametrize
async def test_method_get_with_all_params(self, async_client: AsyncOpencode) -> None:
shell = await async_client.shell.get(
- id={},
+ id="id",
location={
"directory": "string",
},
@@ -350,7 +350,7 @@ async def test_method_get_with_all_params(self, async_client: AsyncOpencode) ->
@parametrize
async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
response = await async_client.shell.with_raw_response.get(
- id={},
+ id="id",
)
assert response.is_closed is True
@@ -361,7 +361,7 @@ async def test_raw_response_get(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_get(self, async_client: AsyncOpencode) -> None:
async with async_client.shell.with_streaming_response.get(
- id={},
+ id="id",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -381,14 +381,14 @@ async def test_path_params_get(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_output(self, async_client: AsyncOpencode) -> None:
shell = await async_client.shell.output(
- id={},
+ id="id",
)
assert_matches_type(ShellOutputResponse, shell, path=["response"])
@parametrize
async def test_method_output_with_all_params(self, async_client: AsyncOpencode) -> None:
shell = await async_client.shell.output(
- id={},
+ id="id",
cursor=0,
limit=0,
location={
@@ -400,7 +400,7 @@ async def test_method_output_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_output(self, async_client: AsyncOpencode) -> None:
response = await async_client.shell.with_raw_response.output(
- id={},
+ id="id",
)
assert response.is_closed is True
@@ -411,7 +411,7 @@ async def test_raw_response_output(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_output(self, async_client: AsyncOpencode) -> None:
async with async_client.shell.with_streaming_response.output(
- id={},
+ id="id",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -431,14 +431,14 @@ async def test_path_params_output(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_remove(self, async_client: AsyncOpencode) -> None:
shell = await async_client.shell.remove(
- id={},
+ id="id",
)
assert shell is None
@parametrize
async def test_method_remove_with_all_params(self, async_client: AsyncOpencode) -> None:
shell = await async_client.shell.remove(
- id={},
+ id="id",
location={
"directory": "string",
},
@@ -448,7 +448,7 @@ async def test_method_remove_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
response = await async_client.shell.with_raw_response.remove(
- id={},
+ id="id",
)
assert response.is_closed is True
@@ -459,7 +459,7 @@ async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_remove(self, async_client: AsyncOpencode) -> None:
async with async_client.shell.with_streaming_response.remove(
- id={},
+ id="id",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/test_vcs.py b/tests/api_resources/test_vcs.py
index ee95198..382bbef 100644
--- a/tests/api_resources/test_vcs.py
+++ b/tests/api_resources/test_vcs.py
@@ -59,14 +59,14 @@ def test_streaming_response_base(self, client: Opencode) -> None:
@parametrize
def test_method_diff(self, client: Opencode) -> None:
vc = client.vcs.diff(
- mode={},
+ mode="working",
)
assert_matches_type(VcDiffResponse, vc, path=["response"])
@parametrize
def test_method_diff_with_all_params(self, client: Opencode) -> None:
vc = client.vcs.diff(
- mode={},
+ mode="working",
base="base",
context=0,
location={
@@ -78,7 +78,7 @@ def test_method_diff_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_diff(self, client: Opencode) -> None:
response = client.vcs.with_raw_response.diff(
- mode={},
+ mode="working",
)
assert response.is_closed is True
@@ -89,7 +89,7 @@ def test_raw_response_diff(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_diff(self, client: Opencode) -> None:
with client.vcs.with_streaming_response.diff(
- mode={},
+ mode="working",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -245,14 +245,14 @@ async def test_streaming_response_base(self, async_client: AsyncOpencode) -> Non
@parametrize
async def test_method_diff(self, async_client: AsyncOpencode) -> None:
vc = await async_client.vcs.diff(
- mode={},
+ mode="working",
)
assert_matches_type(VcDiffResponse, vc, path=["response"])
@parametrize
async def test_method_diff_with_all_params(self, async_client: AsyncOpencode) -> None:
vc = await async_client.vcs.diff(
- mode={},
+ mode="working",
base="base",
context=0,
location={
@@ -264,7 +264,7 @@ async def test_method_diff_with_all_params(self, async_client: AsyncOpencode) ->
@parametrize
async def test_raw_response_diff(self, async_client: AsyncOpencode) -> None:
response = await async_client.vcs.with_raw_response.diff(
- mode={},
+ mode="working",
)
assert response.is_closed is True
@@ -275,7 +275,7 @@ async def test_raw_response_diff(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_diff(self, async_client: AsyncOpencode) -> None:
async with async_client.vcs.with_streaming_response.diff(
- mode={},
+ mode="working",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/test_websearch.py b/tests/api_resources/test_websearch.py
index 26f9128..47147a0 100644
--- a/tests/api_resources/test_websearch.py
+++ b/tests/api_resources/test_websearch.py
@@ -65,7 +65,7 @@ def test_method_query_with_all_params(self, client: Opencode) -> None:
location={
"directory": "string",
},
- provider_id={},
+ provider_id="providerID",
)
assert_matches_type(WebsearchQueryResponse, websearch, path=["response"])
@@ -147,7 +147,7 @@ async def test_method_query_with_all_params(self, async_client: AsyncOpencode) -
location={
"directory": "string",
},
- provider_id={},
+ provider_id="providerID",
)
assert_matches_type(WebsearchQueryResponse, websearch, path=["response"])
diff --git a/tests/api_resources/test_worktree.py b/tests/api_resources/test_worktree.py
index 365bf45..65c1452 100644
--- a/tests/api_resources/test_worktree.py
+++ b/tests/api_resources/test_worktree.py
@@ -20,17 +20,17 @@ class TestWorktree:
@parametrize
def test_method_create(self, client: Opencode) -> None:
worktree = client.worktree.create(
- project_id={},
+ project_id="projectID",
)
assert_matches_type(WorktreeInfo, worktree, path=["response"])
@parametrize
def test_method_create_with_all_params(self, client: Opencode) -> None:
worktree = client.worktree.create(
- project_id={},
+ project_id="projectID",
branch="branch",
- directory={},
- from_={},
+ directory="directory",
+ from_="from",
name="name",
)
assert_matches_type(WorktreeInfo, worktree, path=["response"])
@@ -38,7 +38,7 @@ def test_method_create_with_all_params(self, client: Opencode) -> None:
@parametrize
def test_raw_response_create(self, client: Opencode) -> None:
response = client.worktree.with_raw_response.create(
- project_id={},
+ project_id="projectID",
)
assert response.is_closed is True
@@ -49,7 +49,7 @@ def test_raw_response_create(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_create(self, client: Opencode) -> None:
with client.worktree.with_streaming_response.create(
- project_id={},
+ project_id="projectID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -62,14 +62,14 @@ def test_streaming_response_create(self, client: Opencode) -> None:
@parametrize
def test_method_list(self, client: Opencode) -> None:
worktree = client.worktree.list(
- project_id={},
+ project_id="projectID",
)
assert_matches_type(WorktreeList, worktree, path=["response"])
@parametrize
def test_raw_response_list(self, client: Opencode) -> None:
response = client.worktree.with_raw_response.list(
- project_id={},
+ project_id="projectID",
)
assert response.is_closed is True
@@ -80,7 +80,7 @@ def test_raw_response_list(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_list(self, client: Opencode) -> None:
with client.worktree.with_streaming_response.list(
- project_id={},
+ project_id="projectID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -93,14 +93,14 @@ def test_streaming_response_list(self, client: Opencode) -> None:
@parametrize
def test_method_refresh(self, client: Opencode) -> None:
worktree = client.worktree.refresh(
- project_id={},
+ project_id="projectID",
)
assert worktree is None
@parametrize
def test_raw_response_refresh(self, client: Opencode) -> None:
response = client.worktree.with_raw_response.refresh(
- project_id={},
+ project_id="projectID",
)
assert response.is_closed is True
@@ -111,7 +111,7 @@ def test_raw_response_refresh(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_refresh(self, client: Opencode) -> None:
with client.worktree.with_streaming_response.refresh(
- project_id={},
+ project_id="projectID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -124,18 +124,18 @@ def test_streaming_response_refresh(self, client: Opencode) -> None:
@parametrize
def test_method_remove(self, client: Opencode) -> None:
worktree = client.worktree.remove(
- directory={},
+ directory="directory",
force=True,
- project_id={},
+ project_id="projectID",
)
assert worktree is None
@parametrize
def test_raw_response_remove(self, client: Opencode) -> None:
response = client.worktree.with_raw_response.remove(
- directory={},
+ directory="directory",
force=True,
- project_id={},
+ project_id="projectID",
)
assert response.is_closed is True
@@ -146,9 +146,9 @@ def test_raw_response_remove(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_remove(self, client: Opencode) -> None:
with client.worktree.with_streaming_response.remove(
- directory={},
+ directory="directory",
force=True,
- project_id={},
+ project_id="projectID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -167,17 +167,17 @@ class TestAsyncWorktree:
@parametrize
async def test_method_create(self, async_client: AsyncOpencode) -> None:
worktree = await async_client.worktree.create(
- project_id={},
+ project_id="projectID",
)
assert_matches_type(WorktreeInfo, worktree, path=["response"])
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
worktree = await async_client.worktree.create(
- project_id={},
+ project_id="projectID",
branch="branch",
- directory={},
- from_={},
+ directory="directory",
+ from_="from",
name="name",
)
assert_matches_type(WorktreeInfo, worktree, path=["response"])
@@ -185,7 +185,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpencode)
@parametrize
async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
response = await async_client.worktree.with_raw_response.create(
- project_id={},
+ project_id="projectID",
)
assert response.is_closed is True
@@ -196,7 +196,7 @@ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
async with async_client.worktree.with_streaming_response.create(
- project_id={},
+ project_id="projectID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -209,14 +209,14 @@ async def test_streaming_response_create(self, async_client: AsyncOpencode) -> N
@parametrize
async def test_method_list(self, async_client: AsyncOpencode) -> None:
worktree = await async_client.worktree.list(
- project_id={},
+ project_id="projectID",
)
assert_matches_type(WorktreeList, worktree, path=["response"])
@parametrize
async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
response = await async_client.worktree.with_raw_response.list(
- project_id={},
+ project_id="projectID",
)
assert response.is_closed is True
@@ -227,7 +227,7 @@ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
async with async_client.worktree.with_streaming_response.list(
- project_id={},
+ project_id="projectID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -240,14 +240,14 @@ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> Non
@parametrize
async def test_method_refresh(self, async_client: AsyncOpencode) -> None:
worktree = await async_client.worktree.refresh(
- project_id={},
+ project_id="projectID",
)
assert worktree is None
@parametrize
async def test_raw_response_refresh(self, async_client: AsyncOpencode) -> None:
response = await async_client.worktree.with_raw_response.refresh(
- project_id={},
+ project_id="projectID",
)
assert response.is_closed is True
@@ -258,7 +258,7 @@ async def test_raw_response_refresh(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_refresh(self, async_client: AsyncOpencode) -> None:
async with async_client.worktree.with_streaming_response.refresh(
- project_id={},
+ project_id="projectID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -271,18 +271,18 @@ async def test_streaming_response_refresh(self, async_client: AsyncOpencode) ->
@parametrize
async def test_method_remove(self, async_client: AsyncOpencode) -> None:
worktree = await async_client.worktree.remove(
- directory={},
+ directory="directory",
force=True,
- project_id={},
+ project_id="projectID",
)
assert worktree is None
@parametrize
async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
response = await async_client.worktree.with_raw_response.remove(
- directory={},
+ directory="directory",
force=True,
- project_id={},
+ project_id="projectID",
)
assert response.is_closed is True
@@ -293,9 +293,9 @@ async def test_raw_response_remove(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_remove(self, async_client: AsyncOpencode) -> None:
async with async_client.worktree.with_streaming_response.remove(
- directory={},
+ directory="directory",
force=True,
- project_id={},
+ project_id="projectID",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/test_client.py b/tests/test_client.py
index 56961c2..1485379 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -23,7 +23,7 @@
from opencode._types import Omit
from opencode._utils import asyncify
from opencode._models import BaseModel, FinalRequestOptions
-from opencode._exceptions import OpencodeError, APIStatusError, APITimeoutError, APIResponseValidationError
+from opencode._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError
from opencode._base_client import (
DEFAULT_TIMEOUT,
HTTPX_DEFAULT_TIMEOUT,
@@ -434,16 +434,6 @@ def test_default_headers_option(self) -> None:
test_client.close()
test_client2.close()
- def test_validate_headers(self) -> None:
- client = Opencode(base_url=base_url, username=username, password=password, _strict_response_validation=True)
- request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
- assert "Basic" in request.headers.get("Authorization")
-
- with pytest.raises(OpencodeError):
- with update_env(**{"OPENCODE_PASSWORD": Omit()}):
- client2 = Opencode(base_url=base_url, username=None, password=None, _strict_response_validation=True)
- _ = client2
-
def test_default_query_option(self) -> None:
client = Opencode(
base_url=base_url,
@@ -1425,20 +1415,6 @@ async def test_default_headers_option(self) -> None:
await test_client.close()
await test_client2.close()
- def test_validate_headers(self) -> None:
- client = AsyncOpencode(
- base_url=base_url, username=username, password=password, _strict_response_validation=True
- )
- request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
- assert "Basic" in request.headers.get("Authorization")
-
- with pytest.raises(OpencodeError):
- with update_env(**{"OPENCODE_PASSWORD": Omit()}):
- client2 = AsyncOpencode(
- base_url=base_url, username=None, password=None, _strict_response_validation=True
- )
- _ = client2
-
async def test_default_query_option(self) -> None:
client = AsyncOpencode(
base_url=base_url,
From 06e513300413433f6c5b4898eac6d85ccdddfa29 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 17:11:55 +0530
Subject: [PATCH 14/41] Take the agent recipes of the latest generation
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
examples/agent/integration_command_cancel.py | 2 +-
examples/agent/integration_connect_external.py | 2 +-
examples/agent/integration_oauth_cancel.py | 2 +-
examples/agent/permission_create.py | 2 +-
examples/agent/pty_connect_token.py | 2 +-
examples/agent/session_form_create.py | 2 +-
examples/agent/session_inbox_update.py | 2 +-
examples/agent/session_instructions_entry_list.py | 2 +-
examples/agent/session_message_get.py | 2 +-
examples/agent/session_revert_clear.py | 2 +-
examples/agent/worktree_create.py | 2 +-
11 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/examples/agent/integration_command_cancel.py b/examples/agent/integration_command_cancel.py
index 3d982fa..5ebf4e4 100644
--- a/examples/agent/integration_command_cancel.py
+++ b/examples/agent/integration_command_cancel.py
@@ -4,5 +4,5 @@
client = Opencode()
-command = client.integration.command.cancel(integration_id={}, attempt_id={})
+command = client.integration.command.cancel(integration_id="integrationID", attempt_id="attemptID")
print(command)
diff --git a/examples/agent/integration_connect_external.py b/examples/agent/integration_connect_external.py
index ce85d1e..65fcf9d 100644
--- a/examples/agent/integration_connect_external.py
+++ b/examples/agent/integration_connect_external.py
@@ -4,5 +4,5 @@
client = Opencode()
-connect = client.integration.connect.external(integration_id={}, method_id={})
+connect = client.integration.connect.external(integration_id="integrationID", method_id="methodID")
print(connect)
diff --git a/examples/agent/integration_oauth_cancel.py b/examples/agent/integration_oauth_cancel.py
index d9f78a4..a4efb30 100644
--- a/examples/agent/integration_oauth_cancel.py
+++ b/examples/agent/integration_oauth_cancel.py
@@ -4,5 +4,5 @@
client = Opencode()
-oauth = client.integration.oauth.cancel(integration_id={}, attempt_id={})
+oauth = client.integration.oauth.cancel(integration_id="integrationID", attempt_id="attemptID")
print(oauth)
diff --git a/examples/agent/permission_create.py b/examples/agent/permission_create.py
index 75a90e4..48e8e61 100644
--- a/examples/agent/permission_create.py
+++ b/examples/agent/permission_create.py
@@ -4,5 +4,5 @@
client = Opencode()
-permission = client.permission.create(session_id={}, action="action", resources=["string"])
+permission = client.permission.create(session_id="sessionID", action="action", resources=["string"])
print(permission)
diff --git a/examples/agent/pty_connect_token.py b/examples/agent/pty_connect_token.py
index e06aac6..6c01a30 100644
--- a/examples/agent/pty_connect_token.py
+++ b/examples/agent/pty_connect_token.py
@@ -4,5 +4,5 @@
client = Opencode()
-connect = client.pty.connect.token(pty_id={})
+connect = client.pty.connect.token(pty_id="ptyID")
print(connect)
diff --git a/examples/agent/session_form_create.py b/examples/agent/session_form_create.py
index 6050590..6345edb 100644
--- a/examples/agent/session_form_create.py
+++ b/examples/agent/session_form_create.py
@@ -4,5 +4,5 @@
client = Opencode()
-form = client.session.form.create(session_id="sessionID", fields={}, title="title")
+form = client.session.form.create(session_id="sessionID", fields=[{}], title="title")
print(form)
diff --git a/examples/agent/session_inbox_update.py b/examples/agent/session_inbox_update.py
index 7bea368..33b593b 100644
--- a/examples/agent/session_inbox_update.py
+++ b/examples/agent/session_inbox_update.py
@@ -4,5 +4,5 @@
client = Opencode()
-inbox = client.session.inbox.update(session_id={}, inbox_id={}, delivery={})
+inbox = client.session.inbox.update(session_id="sessionID", inbox_id="inboxID", delivery="steer")
print(inbox)
diff --git a/examples/agent/session_instructions_entry_list.py b/examples/agent/session_instructions_entry_list.py
index 7841008..e839900 100644
--- a/examples/agent/session_instructions_entry_list.py
+++ b/examples/agent/session_instructions_entry_list.py
@@ -4,5 +4,5 @@
client = Opencode()
-entry = client.session.instructions.entry.list(session_id={})
+entry = client.session.instructions.entry.list(session_id="sessionID")
print(entry)
diff --git a/examples/agent/session_message_get.py b/examples/agent/session_message_get.py
index 552bb58..ebf2f26 100644
--- a/examples/agent/session_message_get.py
+++ b/examples/agent/session_message_get.py
@@ -4,5 +4,5 @@
client = Opencode()
-message = client.session.message.get(session_id={}, message_id={})
+message = client.session.message.get(session_id="sessionID", message_id="messageID")
print(message)
diff --git a/examples/agent/session_revert_clear.py b/examples/agent/session_revert_clear.py
index f7471ce..5ddf8b2 100644
--- a/examples/agent/session_revert_clear.py
+++ b/examples/agent/session_revert_clear.py
@@ -4,5 +4,5 @@
client = Opencode()
-revert = client.session.revert.clear(session_id={})
+revert = client.session.revert.clear(session_id="sessionID")
print(revert)
diff --git a/examples/agent/worktree_create.py b/examples/agent/worktree_create.py
index 9e53396..94d70b2 100644
--- a/examples/agent/worktree_create.py
+++ b/examples/agent/worktree_create.py
@@ -4,5 +4,5 @@
client = Opencode()
-worktree = client.worktree.create(project_id={})
+worktree = client.worktree.create(project_id="projectID")
print(worktree)
From 3430d8236f60cc3c9638249a3033200fbc9003a7 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 17:19:36 +0530
Subject: [PATCH 15/41] chore: regenerate SDK
Ferrule-Generated-From: ab4acb44d017412067f5e2d5ddeb5139f16ccf0b
---
examples/agent/session_form_create.py | 36 +-
examples/agent/voice_session.py | 9 +-
ferrule.lock | 32 +-
skills/opencode/SKILL.md | 45 +-
src/opencode/_agent/SKILL.md | 45 +-
.../experimental/test_persistent_pty.py | 4 +-
.../api_resources/integration/test_connect.py | 8 +-
tests/api_resources/integration/test_oauth.py | 4 +-
tests/api_resources/permission/test_saved.py | 4 +-
tests/api_resources/session/test_form.py | 344 ++++++-
tests/api_resources/test_credential.py | 72 +-
tests/api_resources/test_generate.py | 12 +-
tests/api_resources/test_mcp.py | 150 ++-
tests/api_resources/test_permission.py | 20 +-
tests/api_resources/test_project.py | 20 +-
tests/api_resources/test_session.py | 856 ++++++++++++++++--
tests/api_resources/test_voice.py | 48 +-
17 files changed, 1531 insertions(+), 178 deletions(-)
diff --git a/examples/agent/session_form_create.py b/examples/agent/session_form_create.py
index 6345edb..cbe439a 100644
--- a/examples/agent/session_form_create.py
+++ b/examples/agent/session_form_create.py
@@ -4,5 +4,39 @@
client = Opencode()
-form = client.session.form.create(session_id="sessionID", fields=[{}], title="title")
+form = client.session.form.create(
+ session_id="sessionID",
+ fields=[
+ {
+ "custom": True,
+ "default": "string",
+ "description": "string",
+ "format": "email",
+ "hidden": True,
+ "key": "string",
+ "maxLength": 0,
+ "minLength": 0,
+ "options": [
+ {
+ "description": "string",
+ "label": "string",
+ "value": "string",
+ }
+ ],
+ "pattern": "string",
+ "placeholder": "string",
+ "required": True,
+ "title": "string",
+ "type": "string",
+ "when": [
+ {
+ "key": "string",
+ "op": "eq",
+ "value": "string",
+ }
+ ],
+ }
+ ],
+ title="title",
+)
print(form)
diff --git a/examples/agent/voice_session.py b/examples/agent/voice_session.py
index e462527..fb50b80 100644
--- a/examples/agent/voice_session.py
+++ b/examples/agent/voice_session.py
@@ -4,5 +4,12 @@
client = Opencode()
-voice = client.voice.session(model="model", transport={})
+voice = client.voice.session(
+ model="model",
+ transport={
+ "dcid": 0,
+ "sdp": "string",
+ "type": "webrtc",
+ },
+)
print(voice)
diff --git a/ferrule.lock b/ferrule.lock
index d873278..c165cfa 100644
--- a/ferrule.lock
+++ b/ferrule.lock
@@ -38,7 +38,7 @@
"examples/agent/rpc_call.py": "c89e2ad5e860682c6da65adbd5b76ce1087fab4abf598fff4f26e03288c50bc4",
"examples/agent/server_info.py": "0070e224f86aab73b5812ab1574a0d95cbae0a24a6805de26993cf33dd621241",
"examples/agent/session_create.py": "f1796fc1c024f01457eda899063930ead30d288a52e678a813675a6d4b604ed6",
- "examples/agent/session_form_create.py": "daeac58a5cd0a5c898e546ee80bef63135fe2d440463dee65b0e9d2d955a9712",
+ "examples/agent/session_form_create.py": "b76b8bb5c0bbf862e63364dcaf96793efdebd5267ea690e5eb9e85ef3167dcb1",
"examples/agent/session_inbox_update.py": "0586f72cbe3ec53325c87d941c839546fb576b45551709adff1d799e6cb4baaf",
"examples/agent/session_instructions_entry_list.py": "850a49ed898caf7c3c358ba58e47716af0af46088e5e4876106cab95082b9476",
"examples/agent/session_message_get.py": "4aa849478ce5cf99c67d138e6586991bf0b5b5ff03c11bc51e78725e570e97c0",
@@ -47,15 +47,15 @@
"examples/agent/skill_list.py": "6a494c2a14749fb27d3cc902478d99686766f05b35ad19cba42259c5ac1c327d",
"examples/agent/vcs_base.py": "861ef19cdd183b27b1ef8ed3170ea41c416b58f8d75f02583c560158290b62b6",
"examples/agent/vcs_branch_list.py": "5d965ec1673776070a6ff0acfb41543a36da785c42df31b801b0554ff03c62b8",
- "examples/agent/voice_session.py": "07ff5b3e962cd6eb27e0c852ca13a84f9af87f2628394f2100ada9e93e069899",
+ "examples/agent/voice_session.py": "632e3c1bf6bae040abf351c0fc2ad18abfe90fe2e6ba5558ca937048410910c2",
"examples/agent/websearch_providers.py": "30bb99ecdaf39b3c2112112a05c3b844aacfd24e298a4e6cdb40286d7954c3b2",
"examples/agent/worktree_create.py": "c5222de9df346efdafc2e7b7c12449aa502a29e309731c54a9581406e0eaaf59",
"llms.txt": "c9d8fa2e8580039de36b140ca06f2cb5558ff3d2b42e16effbd3a46ed682eef4",
"scripts/utils/ruffen-docs.py": "da4c57fe200f40d65dc9cefdfb1fa65245c407daa595b6522f43c44f14b271af",
- "skills/opencode/SKILL.md": "4c7000cf263208e5c18829e52e5ad33f093c54beae487e3e3560f40220daf4a2",
+ "skills/opencode/SKILL.md": "6db853ae6cfec6c88e9207591ed71545d14e9665bdafc993e0994c9b8898c3ca",
"src/opencode/__init__.py": "9706cd16d41b78c7d20f1f32d04196bb7c11ffb5372dae6cf750eef7118db961",
"src/opencode/_agent/AGENTS.md": "7f135189e8ab203aea9abecf018f905e395767cd38c24c2d22c3c1230c04c5e5",
- "src/opencode/_agent/SKILL.md": "4c7000cf263208e5c18829e52e5ad33f093c54beae487e3e3560f40220daf4a2",
+ "src/opencode/_agent/SKILL.md": "6db853ae6cfec6c88e9207591ed71545d14e9665bdafc993e0994c9b8898c3ca",
"src/opencode/_agent/llms.txt": "c9d8fa2e8580039de36b140ca06f2cb5558ff3d2b42e16effbd3a46ed682eef4",
"src/opencode/_base_client.py": "32888441549574c13267ece8bfbc34394bfb41e6bef39633207fa8381805aae7",
"src/opencode/_client.py": "d14617ca1aea9d2266756cceba3e5c16c6b9b2b35fcf489a26128549cbec7684",
@@ -760,11 +760,11 @@
"tests/api_resources/debug/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/debug/test_location.py": "8dc2ed87566aac5a4c6412b90be8ab49ad2049c5f18cfd966efb8158750fe73d",
"tests/api_resources/experimental/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
- "tests/api_resources/experimental/test_persistent_pty.py": "621920b7bc7c616bcc3c368de52fd204c1aac0b736e2271bb8449deb63b5023e",
+ "tests/api_resources/experimental/test_persistent_pty.py": "effd322779465701098e582876b1d2025b4020f7b338c31812d6f6d72672c47b",
"tests/api_resources/integration/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/integration/test_command.py": "7f54f3b02479958b7e94085b1bf560f6297849289ba03582945f7a69b82e5c3b",
- "tests/api_resources/integration/test_connect.py": "7797beeea54a6dee3da8b0affbbdb4e7cc72a3930537874efdaa864c96116309",
- "tests/api_resources/integration/test_oauth.py": "ac268f0f6125c126e03f5d16f762f2a4d699227dbac2126b6356b53734f7a8d3",
+ "tests/api_resources/integration/test_connect.py": "286b94346bc5d6b56dda4e6ab5fd20e02be8b9549506f5e1b524f58b40a25ad6",
+ "tests/api_resources/integration/test_oauth.py": "5c4b935d8eefeb536b9ae6c8999775d02aed1e9a45bb9b7d31b13f281831fb4d",
"tests/api_resources/integration/test_wellknown.py": "6f875993fb7bb0a33acdde7a84b66b06e27d2ba9442b06009932f87de17daa67",
"tests/api_resources/mcp/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/mcp/test_resource.py": "9da53a0f18921b820d1df920ccc86deac6d961766d6a3bfa513ea0bcfe71b1ff",
@@ -772,42 +772,42 @@
"tests/api_resources/migration/test_v1.py": "53cf87accc6a464ab2828563d4f3cc68aab2969b317631d7aec8bbe92a07d21e",
"tests/api_resources/permission/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/permission/test_request.py": "7777ccc174ac2b7fbf310db96b4bca9340f89e198e110b0a4dcdf1a90f61c180",
- "tests/api_resources/permission/test_saved.py": "2d2e6bc35f36ad3b129ebf5e8bf85a97efdfd8ac20d7078b2682987571910f76",
+ "tests/api_resources/permission/test_saved.py": "9e9a62d45511ae1fc00cf7ecf0fc2f32fdd4d1df5ad8d683fc4cd46da5245487",
"tests/api_resources/pty/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/pty/test_connect.py": "fee2827917a519428c3103ec3b6fd6785b7f83f15d190d85ef9e230ee0f0295e",
"tests/api_resources/session/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/session/instructions/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/session/instructions/test_entry.py": "46b598f854ae1c7a63239751e12e8d23d65e5012f4a053129d8976a9b053a23b",
- "tests/api_resources/session/test_form.py": "17ee2b31db56e53b905629895a95820bb73d67c415ab15499aa24259ac3985aa",
+ "tests/api_resources/session/test_form.py": "49f4212fc26fccb69ab6a7d160961cfb99284a95d154c61a7b3618c4d890d189",
"tests/api_resources/session/test_inbox.py": "c89953a98bca37a57cb76a668a5ef23ed8cf6fdb94388146ceb9316e6e448b45",
"tests/api_resources/session/test_message.py": "9f02aa1f294d39aac79ed24daa2b65e3b78666830b281014ddaa76f4e33c138f",
"tests/api_resources/session/test_revert.py": "e1e1df2eb510bfa0d9d57299d61eef9bd4f2c5eaa4e2e99937fa22ada21f78a9",
"tests/api_resources/test_agent.py": "7f6c7a832a84884620e1da85e4b2bb8529ee711ff31cec23563f4fa9d1d51a2a",
"tests/api_resources/test_command.py": "8be3e96e7ff2ad6750fc1085ce6d422ca8e1cda3b2443dca2de0953c8f9edee5",
"tests/api_resources/test_config.py": "3b646f5206a3143c00ad183ba3a1f7cbc0a588058e8ab9751d316609cd5da218",
- "tests/api_resources/test_credential.py": "49d2dcf793a8c515774142199582929e991de383170e12a1a2b4b49b4e77f451",
+ "tests/api_resources/test_credential.py": "3158903c567e31b5938f1efdab5c8a214284eafe1a46059289c3216e8c74eb26",
"tests/api_resources/test_event.py": "dea5556b0932db3ed178262dd925e3aeeda69783ebf313de0b6c20dd2f600205",
"tests/api_resources/test_file.py": "6b62a1a08c9a76d179e5829e530986df09586373a747986667edda47f4977e33",
"tests/api_resources/test_form.py": "35fdd2d079242efe0a6c26c06198910ed89a2cc1421e5f74509dd202fd830ae5",
- "tests/api_resources/test_generate.py": "7c4b732035560457d0871957f2f686298eff06f23244f9ec3be4c69296b3576b",
+ "tests/api_resources/test_generate.py": "f4156c880368a6bcfce1b68220835fdfe17e9704f874a783be46397b71b69bf6",
"tests/api_resources/test_integration.py": "e2ae3ea7fcff53120d0ee3c990f6bc7517e281cd03909e3f74c33cf535269bcc",
"tests/api_resources/test_location.py": "40f6986acf63ed7e0e279c9df41e970405ac2474f95928f28112921d8d16f13d",
- "tests/api_resources/test_mcp.py": "e99fa31e92a226799a058e9408a9b9bf3ac9290169a8a40019024aae73fe188b",
+ "tests/api_resources/test_mcp.py": "aa097634cc812da7210cbbfd3364faf2e4abf7ed6df03a45240a50e3e194b077",
"tests/api_resources/test_message.py": "797e28f5b7d53858478f346ccb89051c48fe6134952a409461f897ae4adaf74e",
"tests/api_resources/test_model.py": "afd0aa11174a17f25cb7a060597d913202359074c20676ee8b0dabed853548bd",
- "tests/api_resources/test_permission.py": "54c03d5007e1be9e9f05fcfac1fdcf1373ad7506ba11a926eb66a78f6ea71d1a",
+ "tests/api_resources/test_permission.py": "ff26711b38971fdc8833432996b19d821a9f1f9de4b973f239e7eb35c44da2af",
"tests/api_resources/test_plugin.py": "2685fa322627066f274e068959165468524db459aa47684976f57e13f35bb05b",
- "tests/api_resources/test_project.py": "de21eebfa92b08ced8035a43ee783fb8a9805cb30b4d80b703c37449070bfae9",
+ "tests/api_resources/test_project.py": "726fcff5e8fd957199484659e32fac34a90195bb731a1710d1c9c1cec6d3d6ed",
"tests/api_resources/test_provider.py": "e83de14c66e0265613e5c0afd79cae2935a501699ee03d2aa184c21c9117b6b5",
"tests/api_resources/test_pty.py": "cfb5b62e31ccfea29e643ec9eb6b7bbc597aa6a3f57c2662e6fd8aac50a57c25",
"tests/api_resources/test_reference.py": "82661f6cecc6f0fe44d63fd1180817b27869845578f5d71d039916ee1679b8ec",
"tests/api_resources/test_rpc.py": "1dabbd8dbdab2902c05721ea5c344bd57f4c4e5c85af15c9ff60dd650baf4907",
"tests/api_resources/test_server.py": "bf49f7693308b7ede9fd1cf2e4e555dce88ec560c6e908393020e3ebc7fac9db",
- "tests/api_resources/test_session.py": "77932efdee0b5f69066a17aed4816b14f87849bc1465d586b9169316d1cfaf86",
+ "tests/api_resources/test_session.py": "c6d85bd4dbe8a37207c1ac6a6e449bc2456a145f3461e68dd39472426973bcab",
"tests/api_resources/test_shell.py": "544c8e14854989a874fc1e6e5ca9067ccae4bfd44f81ea8a845fa101176181d4",
"tests/api_resources/test_skill.py": "cf22c66273943ec809067cfac8d6b9d14f71e93f862b1be615ae2c51933c34cf",
"tests/api_resources/test_vcs.py": "502249bc039765b74d0860cc151f48f1cc5f2ad4456d123b2c950253411e6bb1",
- "tests/api_resources/test_voice.py": "8db95fb7a02962a82459891692c404f897b10e0670e0da6d6e27f451a0e4480c",
+ "tests/api_resources/test_voice.py": "9ed1ef04915dab2324f66fcafa09f6f4187c312a83fab6cdc458cb301e5ab703",
"tests/api_resources/test_websearch.py": "2bb8b9c82ab1a8f74ec1b057d751e90be6816d82d131876226c651b3a3644f4c",
"tests/api_resources/test_worktree.py": "3a7a0f96d07471e8176011b1bd06bb7884585b04b203a3f5a42336c5ecb45b26",
"tests/api_resources/vcs/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
diff --git a/skills/opencode/SKILL.md b/skills/opencode/SKILL.md
index 5d2a604..ea64563 100644
--- a/skills/opencode/SKILL.md
+++ b/skills/opencode/SKILL.md
@@ -448,7 +448,41 @@ from opencode import Opencode
client = Opencode()
-form = client.session.form.create(session_id="sessionID", fields=[{}], title="title")
+form = client.session.form.create(
+ session_id="sessionID",
+ fields=[
+ {
+ "custom": True,
+ "default": "string",
+ "description": "string",
+ "format": "email",
+ "hidden": True,
+ "key": "string",
+ "maxLength": 0,
+ "minLength": 0,
+ "options": [
+ {
+ "description": "string",
+ "label": "string",
+ "value": "string",
+ }
+ ],
+ "pattern": "string",
+ "placeholder": "string",
+ "required": True,
+ "title": "string",
+ "type": "string",
+ "when": [
+ {
+ "key": "string",
+ "op": "eq",
+ "value": "string",
+ }
+ ],
+ }
+ ],
+ title="title",
+)
print(form)
```
@@ -503,7 +537,14 @@ from opencode import Opencode
client = Opencode()
-voice = client.voice.session(model="model", transport={})
+voice = client.voice.session(
+ model="model",
+ transport={
+ "dcid": 0,
+ "sdp": "string",
+ "type": "webrtc",
+ },
+)
print(voice)
```
diff --git a/src/opencode/_agent/SKILL.md b/src/opencode/_agent/SKILL.md
index 5d2a604..ea64563 100644
--- a/src/opencode/_agent/SKILL.md
+++ b/src/opencode/_agent/SKILL.md
@@ -448,7 +448,41 @@ from opencode import Opencode
client = Opencode()
-form = client.session.form.create(session_id="sessionID", fields=[{}], title="title")
+form = client.session.form.create(
+ session_id="sessionID",
+ fields=[
+ {
+ "custom": True,
+ "default": "string",
+ "description": "string",
+ "format": "email",
+ "hidden": True,
+ "key": "string",
+ "maxLength": 0,
+ "minLength": 0,
+ "options": [
+ {
+ "description": "string",
+ "label": "string",
+ "value": "string",
+ }
+ ],
+ "pattern": "string",
+ "placeholder": "string",
+ "required": True,
+ "title": "string",
+ "type": "string",
+ "when": [
+ {
+ "key": "string",
+ "op": "eq",
+ "value": "string",
+ }
+ ],
+ }
+ ],
+ title="title",
+)
print(form)
```
@@ -503,7 +537,14 @@ from opencode import Opencode
client = Opencode()
-voice = client.voice.session(model="model", transport={})
+voice = client.voice.session(
+ model="model",
+ transport={
+ "dcid": 0,
+ "sdp": "string",
+ "type": "webrtc",
+ },
+)
print(voice)
```
diff --git a/tests/api_resources/experimental/test_persistent_pty.py b/tests/api_resources/experimental/test_persistent_pty.py
index aa49317..b0a18c0 100644
--- a/tests/api_resources/experimental/test_persistent_pty.py
+++ b/tests/api_resources/experimental/test_persistent_pty.py
@@ -316,7 +316,7 @@ def test_method_read(self, client: Opencode) -> None:
def test_method_read_with_all_params(self, client: Opencode) -> None:
persistent_pty = client.experimental.persistent_pty.read(
session_id="sessionID",
- lines={},
+ lines=0,
)
assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
@@ -748,7 +748,7 @@ async def test_method_read(self, async_client: AsyncOpencode) -> None:
async def test_method_read_with_all_params(self, async_client: AsyncOpencode) -> None:
persistent_pty = await async_client.experimental.persistent_pty.read(
session_id="sessionID",
- lines={},
+ lines=0,
)
assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
diff --git a/tests/api_resources/integration/test_connect.py b/tests/api_resources/integration/test_connect.py
index 0081969..4ffbfd9 100644
--- a/tests/api_resources/integration/test_connect.py
+++ b/tests/api_resources/integration/test_connect.py
@@ -28,7 +28,7 @@ def test_method_external_with_all_params(self, client: Opencode) -> None:
connect = client.integration.connect.external(
integration_id="integrationID",
method_id="methodID",
- answer={},
+ answer={"foo": "string"},
label="label",
location={
"directory": "string",
@@ -83,7 +83,7 @@ def test_method_key_with_all_params(self, client: Opencode) -> None:
connect = client.integration.connect.key(
integration_id="integrationID",
key="key",
- answer={},
+ answer={"foo": "string"},
label="label",
location={
"directory": "string",
@@ -144,7 +144,7 @@ async def test_method_external_with_all_params(self, async_client: AsyncOpencode
connect = await async_client.integration.connect.external(
integration_id="integrationID",
method_id="methodID",
- answer={},
+ answer={"foo": "string"},
label="label",
location={
"directory": "string",
@@ -199,7 +199,7 @@ async def test_method_key_with_all_params(self, async_client: AsyncOpencode) ->
connect = await async_client.integration.connect.key(
integration_id="integrationID",
key="key",
- answer={},
+ answer={"foo": "string"},
label="label",
location={
"directory": "string",
diff --git a/tests/api_resources/integration/test_oauth.py b/tests/api_resources/integration/test_oauth.py
index c48eef9..3d44bb0 100644
--- a/tests/api_resources/integration/test_oauth.py
+++ b/tests/api_resources/integration/test_oauth.py
@@ -149,7 +149,7 @@ def test_method_connect_with_all_params(self, client: Opencode) -> None:
oauth = client.integration.oauth.connect(
integration_id="integrationID",
method_id="methodID",
- answer={},
+ answer={"foo": "string"},
label="label",
location={
"directory": "string",
@@ -388,7 +388,7 @@ async def test_method_connect_with_all_params(self, async_client: AsyncOpencode)
oauth = await async_client.integration.oauth.connect(
integration_id="integrationID",
method_id="methodID",
- answer={},
+ answer={"foo": "string"},
label="label",
location={
"directory": "string",
diff --git a/tests/api_resources/permission/test_saved.py b/tests/api_resources/permission/test_saved.py
index 12711b4..3d4be2a 100644
--- a/tests/api_resources/permission/test_saved.py
+++ b/tests/api_resources/permission/test_saved.py
@@ -73,7 +73,7 @@ def test_method_list(self, client: Opencode) -> None:
@parametrize
def test_method_list_with_all_params(self, client: Opencode) -> None:
saved = client.permission.saved.list(
- project_id={},
+ project_id="projectID",
)
assert_matches_type(SavedListResponse, saved, path=["response"])
@@ -196,7 +196,7 @@ async def test_method_list(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
saved = await async_client.permission.saved.list(
- project_id={},
+ project_id="projectID",
)
assert_matches_type(SavedListResponse, saved, path=["response"])
diff --git a/tests/api_resources/session/test_form.py b/tests/api_resources/session/test_form.py
index 463774c..d1b8c56 100644
--- a/tests/api_resources/session/test_form.py
+++ b/tests/api_resources/session/test_form.py
@@ -22,7 +22,37 @@ class TestForm:
def test_method_create(self, client: Opencode) -> None:
form = client.session.form.create(
session_id="sessionID",
- fields=[{}],
+ fields=[
+ {
+ "custom": True,
+ "default": "string",
+ "description": "string",
+ "format": "email",
+ "hidden": True,
+ "key": "string",
+ "maxLength": 0,
+ "minLength": 0,
+ "options": [
+ {
+ "description": "string",
+ "label": "string",
+ "value": "string",
+ }
+ ],
+ "pattern": "string",
+ "placeholder": "string",
+ "required": True,
+ "title": "string",
+ "type": "string",
+ "when": [
+ {
+ "key": "string",
+ "op": "eq",
+ "value": "string",
+ }
+ ],
+ }
+ ],
title="title",
)
assert_matches_type(FormInfo, form, path=["response"])
@@ -31,9 +61,39 @@ def test_method_create(self, client: Opencode) -> None:
def test_method_create_with_all_params(self, client: Opencode) -> None:
form = client.session.form.create(
session_id="sessionID",
- fields=[{}],
+ fields=[
+ {
+ "custom": True,
+ "default": "string",
+ "description": "string",
+ "format": "email",
+ "hidden": True,
+ "key": "string",
+ "maxLength": 0,
+ "minLength": 0,
+ "options": [
+ {
+ "description": "string",
+ "label": "string",
+ "value": "string",
+ }
+ ],
+ "pattern": "string",
+ "placeholder": "string",
+ "required": True,
+ "title": "string",
+ "type": "string",
+ "when": [
+ {
+ "key": "string",
+ "op": "eq",
+ "value": "string",
+ }
+ ],
+ }
+ ],
title="title",
- id={},
+ id="id",
metadata={"foo": {}},
)
assert_matches_type(FormInfo, form, path=["response"])
@@ -42,7 +102,37 @@ def test_method_create_with_all_params(self, client: Opencode) -> None:
def test_raw_response_create(self, client: Opencode) -> None:
response = client.session.form.with_raw_response.create(
session_id="sessionID",
- fields=[{}],
+ fields=[
+ {
+ "custom": True,
+ "default": "string",
+ "description": "string",
+ "format": "email",
+ "hidden": True,
+ "key": "string",
+ "maxLength": 0,
+ "minLength": 0,
+ "options": [
+ {
+ "description": "string",
+ "label": "string",
+ "value": "string",
+ }
+ ],
+ "pattern": "string",
+ "placeholder": "string",
+ "required": True,
+ "title": "string",
+ "type": "string",
+ "when": [
+ {
+ "key": "string",
+ "op": "eq",
+ "value": "string",
+ }
+ ],
+ }
+ ],
title="title",
)
@@ -55,7 +145,37 @@ def test_raw_response_create(self, client: Opencode) -> None:
def test_streaming_response_create(self, client: Opencode) -> None:
with client.session.form.with_streaming_response.create(
session_id="sessionID",
- fields=[{}],
+ fields=[
+ {
+ "custom": True,
+ "default": "string",
+ "description": "string",
+ "format": "email",
+ "hidden": True,
+ "key": "string",
+ "maxLength": 0,
+ "minLength": 0,
+ "options": [
+ {
+ "description": "string",
+ "label": "string",
+ "value": "string",
+ }
+ ],
+ "pattern": "string",
+ "placeholder": "string",
+ "required": True,
+ "title": "string",
+ "type": "string",
+ "when": [
+ {
+ "key": "string",
+ "op": "eq",
+ "value": "string",
+ }
+ ],
+ }
+ ],
title="title",
) as response:
assert not response.is_closed
@@ -71,7 +191,37 @@ def test_path_params_create(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
client.session.form.with_raw_response.create(
session_id="",
- fields=[{}],
+ fields=[
+ {
+ "custom": True,
+ "default": "string",
+ "description": "string",
+ "format": "email",
+ "hidden": True,
+ "key": "string",
+ "maxLength": 0,
+ "minLength": 0,
+ "options": [
+ {
+ "description": "string",
+ "label": "string",
+ "value": "string",
+ }
+ ],
+ "pattern": "string",
+ "placeholder": "string",
+ "required": True,
+ "title": "string",
+ "type": "string",
+ "when": [
+ {
+ "key": "string",
+ "op": "eq",
+ "value": "string",
+ }
+ ],
+ }
+ ],
title="title",
)
@@ -223,7 +373,7 @@ def test_method_reply(self, client: Opencode) -> None:
form = client.session.form.reply(
form_id="formID",
session_id="sessionID",
- answer={"foo": {}},
+ answer={"foo": "string"},
)
assert form is None
@@ -232,7 +382,7 @@ def test_raw_response_reply(self, client: Opencode) -> None:
response = client.session.form.with_raw_response.reply(
form_id="formID",
session_id="sessionID",
- answer={"foo": {}},
+ answer={"foo": "string"},
)
assert response.is_closed is True
@@ -245,7 +395,7 @@ def test_streaming_response_reply(self, client: Opencode) -> None:
with client.session.form.with_streaming_response.reply(
form_id="formID",
session_id="sessionID",
- answer={"foo": {}},
+ answer={"foo": "string"},
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -261,14 +411,14 @@ def test_path_params_reply(self, client: Opencode) -> None:
client.session.form.with_raw_response.reply(
form_id="formID",
session_id="",
- answer={"foo": {}},
+ answer={"foo": "string"},
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `form_id` but received ''"):
client.session.form.with_raw_response.reply(
form_id="",
session_id="sessionID",
- answer={"foo": {}},
+ answer={"foo": "string"},
)
@@ -281,7 +431,37 @@ class TestAsyncForm:
async def test_method_create(self, async_client: AsyncOpencode) -> None:
form = await async_client.session.form.create(
session_id="sessionID",
- fields=[{}],
+ fields=[
+ {
+ "custom": True,
+ "default": "string",
+ "description": "string",
+ "format": "email",
+ "hidden": True,
+ "key": "string",
+ "maxLength": 0,
+ "minLength": 0,
+ "options": [
+ {
+ "description": "string",
+ "label": "string",
+ "value": "string",
+ }
+ ],
+ "pattern": "string",
+ "placeholder": "string",
+ "required": True,
+ "title": "string",
+ "type": "string",
+ "when": [
+ {
+ "key": "string",
+ "op": "eq",
+ "value": "string",
+ }
+ ],
+ }
+ ],
title="title",
)
assert_matches_type(FormInfo, form, path=["response"])
@@ -290,9 +470,39 @@ async def test_method_create(self, async_client: AsyncOpencode) -> None:
async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
form = await async_client.session.form.create(
session_id="sessionID",
- fields=[{}],
+ fields=[
+ {
+ "custom": True,
+ "default": "string",
+ "description": "string",
+ "format": "email",
+ "hidden": True,
+ "key": "string",
+ "maxLength": 0,
+ "minLength": 0,
+ "options": [
+ {
+ "description": "string",
+ "label": "string",
+ "value": "string",
+ }
+ ],
+ "pattern": "string",
+ "placeholder": "string",
+ "required": True,
+ "title": "string",
+ "type": "string",
+ "when": [
+ {
+ "key": "string",
+ "op": "eq",
+ "value": "string",
+ }
+ ],
+ }
+ ],
title="title",
- id={},
+ id="id",
metadata={"foo": {}},
)
assert_matches_type(FormInfo, form, path=["response"])
@@ -301,7 +511,37 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpencode)
async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.form.with_raw_response.create(
session_id="sessionID",
- fields=[{}],
+ fields=[
+ {
+ "custom": True,
+ "default": "string",
+ "description": "string",
+ "format": "email",
+ "hidden": True,
+ "key": "string",
+ "maxLength": 0,
+ "minLength": 0,
+ "options": [
+ {
+ "description": "string",
+ "label": "string",
+ "value": "string",
+ }
+ ],
+ "pattern": "string",
+ "placeholder": "string",
+ "required": True,
+ "title": "string",
+ "type": "string",
+ "when": [
+ {
+ "key": "string",
+ "op": "eq",
+ "value": "string",
+ }
+ ],
+ }
+ ],
title="title",
)
@@ -314,7 +554,37 @@ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
async with async_client.session.form.with_streaming_response.create(
session_id="sessionID",
- fields=[{}],
+ fields=[
+ {
+ "custom": True,
+ "default": "string",
+ "description": "string",
+ "format": "email",
+ "hidden": True,
+ "key": "string",
+ "maxLength": 0,
+ "minLength": 0,
+ "options": [
+ {
+ "description": "string",
+ "label": "string",
+ "value": "string",
+ }
+ ],
+ "pattern": "string",
+ "placeholder": "string",
+ "required": True,
+ "title": "string",
+ "type": "string",
+ "when": [
+ {
+ "key": "string",
+ "op": "eq",
+ "value": "string",
+ }
+ ],
+ }
+ ],
title="title",
) as response:
assert not response.is_closed
@@ -330,7 +600,37 @@ async def test_path_params_create(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
await async_client.session.form.with_raw_response.create(
session_id="",
- fields=[{}],
+ fields=[
+ {
+ "custom": True,
+ "default": "string",
+ "description": "string",
+ "format": "email",
+ "hidden": True,
+ "key": "string",
+ "maxLength": 0,
+ "minLength": 0,
+ "options": [
+ {
+ "description": "string",
+ "label": "string",
+ "value": "string",
+ }
+ ],
+ "pattern": "string",
+ "placeholder": "string",
+ "required": True,
+ "title": "string",
+ "type": "string",
+ "when": [
+ {
+ "key": "string",
+ "op": "eq",
+ "value": "string",
+ }
+ ],
+ }
+ ],
title="title",
)
@@ -482,7 +782,7 @@ async def test_method_reply(self, async_client: AsyncOpencode) -> None:
form = await async_client.session.form.reply(
form_id="formID",
session_id="sessionID",
- answer={"foo": {}},
+ answer={"foo": "string"},
)
assert form is None
@@ -491,7 +791,7 @@ async def test_raw_response_reply(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.form.with_raw_response.reply(
form_id="formID",
session_id="sessionID",
- answer={"foo": {}},
+ answer={"foo": "string"},
)
assert response.is_closed is True
@@ -504,7 +804,7 @@ async def test_streaming_response_reply(self, async_client: AsyncOpencode) -> No
async with async_client.session.form.with_streaming_response.reply(
form_id="formID",
session_id="sessionID",
- answer={"foo": {}},
+ answer={"foo": "string"},
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -520,12 +820,12 @@ async def test_path_params_reply(self, async_client: AsyncOpencode) -> None:
await async_client.session.form.with_raw_response.reply(
form_id="formID",
session_id="",
- answer={"foo": {}},
+ answer={"foo": "string"},
)
with pytest.raises(ValueError, match=r"Expected a non-empty value for `form_id` but received ''"):
await async_client.session.form.with_raw_response.reply(
form_id="",
session_id="sessionID",
- answer={"foo": {}},
+ answer={"foo": "string"},
)
diff --git a/tests/api_resources/test_credential.py b/tests/api_resources/test_credential.py
index 3ce575b..b3e3c4f 100644
--- a/tests/api_resources/test_credential.py
+++ b/tests/api_resources/test_credential.py
@@ -22,7 +22,14 @@ class TestCredential:
def test_method_create(self, client: Opencode) -> None:
credential = client.credential.create(
integration_id="integrationID",
- value={},
+ value={
+ "access": "string",
+ "expires": 0,
+ "metadata": {"foo": {}},
+ "methodID": "string",
+ "refresh": "string",
+ "type": "oauth",
+ },
)
assert_matches_type(CredentialEntry, credential, path=["response"])
@@ -30,7 +37,14 @@ def test_method_create(self, client: Opencode) -> None:
def test_method_create_with_all_params(self, client: Opencode) -> None:
credential = client.credential.create(
integration_id="integrationID",
- value={},
+ value={
+ "access": "string",
+ "expires": 0,
+ "metadata": {"foo": {}},
+ "methodID": "string",
+ "refresh": "string",
+ "type": "oauth",
+ },
activate=True,
id="id",
label="label",
@@ -41,7 +55,14 @@ def test_method_create_with_all_params(self, client: Opencode) -> None:
def test_raw_response_create(self, client: Opencode) -> None:
response = client.credential.with_raw_response.create(
integration_id="integrationID",
- value={},
+ value={
+ "access": "string",
+ "expires": 0,
+ "metadata": {"foo": {}},
+ "methodID": "string",
+ "refresh": "string",
+ "type": "oauth",
+ },
)
assert response.is_closed is True
@@ -53,7 +74,14 @@ def test_raw_response_create(self, client: Opencode) -> None:
def test_streaming_response_create(self, client: Opencode) -> None:
with client.credential.with_streaming_response.create(
integration_id="integrationID",
- value={},
+ value={
+ "access": "string",
+ "expires": 0,
+ "metadata": {"foo": {}},
+ "methodID": "string",
+ "refresh": "string",
+ "type": "oauth",
+ },
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -216,7 +244,14 @@ class TestAsyncCredential:
async def test_method_create(self, async_client: AsyncOpencode) -> None:
credential = await async_client.credential.create(
integration_id="integrationID",
- value={},
+ value={
+ "access": "string",
+ "expires": 0,
+ "metadata": {"foo": {}},
+ "methodID": "string",
+ "refresh": "string",
+ "type": "oauth",
+ },
)
assert_matches_type(CredentialEntry, credential, path=["response"])
@@ -224,7 +259,14 @@ async def test_method_create(self, async_client: AsyncOpencode) -> None:
async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
credential = await async_client.credential.create(
integration_id="integrationID",
- value={},
+ value={
+ "access": "string",
+ "expires": 0,
+ "metadata": {"foo": {}},
+ "methodID": "string",
+ "refresh": "string",
+ "type": "oauth",
+ },
activate=True,
id="id",
label="label",
@@ -235,7 +277,14 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpencode)
async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
response = await async_client.credential.with_raw_response.create(
integration_id="integrationID",
- value={},
+ value={
+ "access": "string",
+ "expires": 0,
+ "metadata": {"foo": {}},
+ "methodID": "string",
+ "refresh": "string",
+ "type": "oauth",
+ },
)
assert response.is_closed is True
@@ -247,7 +296,14 @@ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
async def test_streaming_response_create(self, async_client: AsyncOpencode) -> None:
async with async_client.credential.with_streaming_response.create(
integration_id="integrationID",
- value={},
+ value={
+ "access": "string",
+ "expires": 0,
+ "metadata": {"foo": {}},
+ "methodID": "string",
+ "refresh": "string",
+ "type": "oauth",
+ },
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
diff --git a/tests/api_resources/test_generate.py b/tests/api_resources/test_generate.py
index 5a18b72..67da604 100644
--- a/tests/api_resources/test_generate.py
+++ b/tests/api_resources/test_generate.py
@@ -28,7 +28,11 @@ def test_method_text(self, client: Opencode) -> None:
def test_method_text_with_all_params(self, client: Opencode) -> None:
generate = client.generate.text(
prompt="prompt",
- model={},
+ model={
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
)
assert_matches_type(GenerateTextResponse, generate, path=["response"])
@@ -73,7 +77,11 @@ async def test_method_text(self, async_client: AsyncOpencode) -> None:
async def test_method_text_with_all_params(self, async_client: AsyncOpencode) -> None:
generate = await async_client.generate.text(
prompt="prompt",
- model={},
+ model={
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
)
assert_matches_type(GenerateTextResponse, generate, path=["response"])
diff --git a/tests/api_resources/test_mcp.py b/tests/api_resources/test_mcp.py
index 2a85eef..91ac30a 100644
--- a/tests/api_resources/test_mcp.py
+++ b/tests/api_resources/test_mcp.py
@@ -55,7 +55,20 @@ def test_streaming_response_list(self, client: Opencode) -> None:
def test_method_add(self, client: Opencode) -> None:
mcp = client.mcp.add(
server="server",
- config={},
+ config={
+ "codemode": True,
+ "command": ["string"],
+ "cwd": "string",
+ "disabled": True,
+ "environment": {"foo": "string"},
+ "protocol": "legacy",
+ "timeout": {
+ "catalog": 0,
+ "execution": 0,
+ "startup": 0,
+ },
+ "type": "local",
+ },
)
assert mcp is None
@@ -63,7 +76,20 @@ def test_method_add(self, client: Opencode) -> None:
def test_method_add_with_all_params(self, client: Opencode) -> None:
mcp = client.mcp.add(
server="server",
- config={},
+ config={
+ "codemode": True,
+ "command": ["string"],
+ "cwd": "string",
+ "disabled": True,
+ "environment": {"foo": "string"},
+ "protocol": "legacy",
+ "timeout": {
+ "catalog": 0,
+ "execution": 0,
+ "startup": 0,
+ },
+ "type": "local",
+ },
location={
"directory": "string",
},
@@ -74,7 +100,20 @@ def test_method_add_with_all_params(self, client: Opencode) -> None:
def test_raw_response_add(self, client: Opencode) -> None:
response = client.mcp.with_raw_response.add(
server="server",
- config={},
+ config={
+ "codemode": True,
+ "command": ["string"],
+ "cwd": "string",
+ "disabled": True,
+ "environment": {"foo": "string"},
+ "protocol": "legacy",
+ "timeout": {
+ "catalog": 0,
+ "execution": 0,
+ "startup": 0,
+ },
+ "type": "local",
+ },
)
assert response.is_closed is True
@@ -86,7 +125,20 @@ def test_raw_response_add(self, client: Opencode) -> None:
def test_streaming_response_add(self, client: Opencode) -> None:
with client.mcp.with_streaming_response.add(
server="server",
- config={},
+ config={
+ "codemode": True,
+ "command": ["string"],
+ "cwd": "string",
+ "disabled": True,
+ "environment": {"foo": "string"},
+ "protocol": "legacy",
+ "timeout": {
+ "catalog": 0,
+ "execution": 0,
+ "startup": 0,
+ },
+ "type": "local",
+ },
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -101,7 +153,20 @@ def test_path_params_add(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `server` but received ''"):
client.mcp.with_raw_response.add(
server="",
- config={},
+ config={
+ "codemode": True,
+ "command": ["string"],
+ "cwd": "string",
+ "disabled": True,
+ "environment": {"foo": "string"},
+ "protocol": "legacy",
+ "timeout": {
+ "catalog": 0,
+ "execution": 0,
+ "startup": 0,
+ },
+ "type": "local",
+ },
)
@parametrize
@@ -292,7 +357,20 @@ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> Non
async def test_method_add(self, async_client: AsyncOpencode) -> None:
mcp = await async_client.mcp.add(
server="server",
- config={},
+ config={
+ "codemode": True,
+ "command": ["string"],
+ "cwd": "string",
+ "disabled": True,
+ "environment": {"foo": "string"},
+ "protocol": "legacy",
+ "timeout": {
+ "catalog": 0,
+ "execution": 0,
+ "startup": 0,
+ },
+ "type": "local",
+ },
)
assert mcp is None
@@ -300,7 +378,20 @@ async def test_method_add(self, async_client: AsyncOpencode) -> None:
async def test_method_add_with_all_params(self, async_client: AsyncOpencode) -> None:
mcp = await async_client.mcp.add(
server="server",
- config={},
+ config={
+ "codemode": True,
+ "command": ["string"],
+ "cwd": "string",
+ "disabled": True,
+ "environment": {"foo": "string"},
+ "protocol": "legacy",
+ "timeout": {
+ "catalog": 0,
+ "execution": 0,
+ "startup": 0,
+ },
+ "type": "local",
+ },
location={
"directory": "string",
},
@@ -311,7 +402,20 @@ async def test_method_add_with_all_params(self, async_client: AsyncOpencode) ->
async def test_raw_response_add(self, async_client: AsyncOpencode) -> None:
response = await async_client.mcp.with_raw_response.add(
server="server",
- config={},
+ config={
+ "codemode": True,
+ "command": ["string"],
+ "cwd": "string",
+ "disabled": True,
+ "environment": {"foo": "string"},
+ "protocol": "legacy",
+ "timeout": {
+ "catalog": 0,
+ "execution": 0,
+ "startup": 0,
+ },
+ "type": "local",
+ },
)
assert response.is_closed is True
@@ -323,7 +427,20 @@ async def test_raw_response_add(self, async_client: AsyncOpencode) -> None:
async def test_streaming_response_add(self, async_client: AsyncOpencode) -> None:
async with async_client.mcp.with_streaming_response.add(
server="server",
- config={},
+ config={
+ "codemode": True,
+ "command": ["string"],
+ "cwd": "string",
+ "disabled": True,
+ "environment": {"foo": "string"},
+ "protocol": "legacy",
+ "timeout": {
+ "catalog": 0,
+ "execution": 0,
+ "startup": 0,
+ },
+ "type": "local",
+ },
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -338,7 +455,20 @@ async def test_path_params_add(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `server` but received ''"):
await async_client.mcp.with_raw_response.add(
server="",
- config={},
+ config={
+ "codemode": True,
+ "command": ["string"],
+ "cwd": "string",
+ "disabled": True,
+ "environment": {"foo": "string"},
+ "protocol": "legacy",
+ "timeout": {
+ "catalog": 0,
+ "execution": 0,
+ "startup": 0,
+ },
+ "type": "local",
+ },
)
@parametrize
diff --git a/tests/api_resources/test_permission.py b/tests/api_resources/test_permission.py
index 3a48cb5..59280d8 100644
--- a/tests/api_resources/test_permission.py
+++ b/tests/api_resources/test_permission.py
@@ -33,11 +33,15 @@ def test_method_create_with_all_params(self, client: Opencode) -> None:
session_id="sessionID",
action="action",
resources=["string"],
- agent={},
- id={},
+ agent="agent",
+ id="id",
metadata={"foo": {}},
save=["string"],
- source={},
+ source={
+ "id": "string",
+ "messageID": "string",
+ "type": "tool",
+ },
)
assert_matches_type(PermissionCreateResponse, permission, path=["response"])
@@ -248,11 +252,15 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpencode)
session_id="sessionID",
action="action",
resources=["string"],
- agent={},
- id={},
+ agent="agent",
+ id="id",
metadata={"foo": {}},
save=["string"],
- source={},
+ source={
+ "id": "string",
+ "messageID": "string",
+ "type": "tool",
+ },
)
assert_matches_type(PermissionCreateResponse, permission, path=["response"])
diff --git a/tests/api_resources/test_project.py b/tests/api_resources/test_project.py
index 36e5917..b32b407 100644
--- a/tests/api_resources/test_project.py
+++ b/tests/api_resources/test_project.py
@@ -30,8 +30,14 @@ def test_method_update_with_all_params(self, client: Opencode) -> None:
project = client.project.update(
project_id="projectID",
canonical="canonical",
- commands={},
- icon={},
+ commands={
+ "start": "string",
+ },
+ icon={
+ "color": "string",
+ "override": "string",
+ "url": "string",
+ },
name="name",
)
assert_matches_type(Project, project, path=["response"])
@@ -117,8 +123,14 @@ async def test_method_update_with_all_params(self, async_client: AsyncOpencode)
project = await async_client.project.update(
project_id="projectID",
canonical="canonical",
- commands={},
- icon={},
+ commands={
+ "start": "string",
+ },
+ icon={
+ "color": "string",
+ "override": "string",
+ "url": "string",
+ },
name="name",
)
assert_matches_type(Project, project, path=["response"])
diff --git a/tests/api_resources/test_session.py b/tests/api_resources/test_session.py
index 68e1c95..16ab022 100644
--- a/tests/api_resources/test_session.py
+++ b/tests/api_resources/test_session.py
@@ -39,13 +39,25 @@ def test_method_create(self, client: Opencode) -> None:
@parametrize
def test_method_create_with_all_params(self, client: Opencode) -> None:
session = client.session.create(
- agent={},
- id={},
- location={},
- metadata={},
- model={},
- parent_id={},
- permissions={},
+ agent="agent",
+ id="id",
+ location={
+ "directory": "string",
+ },
+ metadata={"foo": {}},
+ model={
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
+ parent_id="parentID",
+ permissions=[
+ {
+ "action": "string",
+ "effect": "allow",
+ "resource": "string",
+ }
+ ],
title="title",
)
assert_matches_type(SessionInfo, session, path=["response"])
@@ -82,8 +94,14 @@ def test_method_update_with_all_params(self, client: Opencode) -> None:
session = client.session.update(
session_id="sessionID",
archived=True,
- metadata={},
- permissions={},
+ metadata={"foo": {}},
+ permissions=[
+ {
+ "action": "string",
+ "effect": "allow",
+ "resource": "string",
+ }
+ ],
title="title",
)
assert session is None
@@ -128,14 +146,14 @@ def test_method_list(self, client: Opencode) -> None:
def test_method_list_with_all_params(self, client: Opencode) -> None:
session = client.session.list(
archived=True,
- cursor={},
- directory={},
+ cursor="cursor",
+ directory="directory",
limit=0,
order="asc",
- parent_id={},
- project={},
+ parent_id="parentID",
+ project="project",
search="search",
- subpath={},
+ subpath="subpath",
)
assert_matches_type(SessionInfo, session, path=["response"])
@@ -237,10 +255,39 @@ def test_method_command_with_all_params(self, client: Opencode) -> None:
session_id="sessionID",
name="name",
text="text",
- agents=[{}],
- delivery={},
- files=[{}],
- skills=[{}],
+ agents=[
+ {
+ "mention": {
+ "end": 0,
+ "start": 0,
+ "text": "string",
+ },
+ "name": "string",
+ }
+ ],
+ delivery="steer",
+ files=[
+ {
+ "description": "string",
+ "mention": {
+ "end": 0,
+ "start": 0,
+ "text": "string",
+ },
+ "name": "string",
+ "uri": "string",
+ }
+ ],
+ skills=[
+ {
+ "id": "string",
+ "mention": {
+ "end": 0,
+ "start": 0,
+ "text": "string",
+ },
+ }
+ ],
)
assert session is None
@@ -292,8 +339,8 @@ def test_method_compact(self, client: Opencode) -> None:
def test_method_compact_with_all_params(self, client: Opencode) -> None:
session = client.session.compact(
session_id="sessionID",
- delivery={},
- id={},
+ delivery="steer",
+ id="id",
)
assert_matches_type(SessionInboxCompaction, session, path=["response"])
@@ -378,8 +425,8 @@ def test_method_diff_with_all_params(self, client: Opencode) -> None:
session = client.session.diff(
session_id="sessionID",
context=0,
- from_={},
- to={},
+ from_="from",
+ to="to",
)
assert_matches_type(SessionDiffResponse, session, path=["response"])
@@ -513,7 +560,7 @@ def test_method_fork(self, client: Opencode) -> None:
def test_method_fork_with_all_params(self, client: Opencode) -> None:
session = client.session.fork(
session_id="sessionID",
- before={},
+ before="before",
)
assert_matches_type(SessionInfo, session, path=["response"])
@@ -631,7 +678,69 @@ def test_path_params_get(self, client: Opencode) -> None:
@parametrize
def test_method_import_(self, client: Opencode) -> None:
session = client.session.import_(
- info={},
+ info={
+ "agent": "string",
+ "cost": 0,
+ "fork": {
+ "boundary": {
+ "messageID": "string",
+ "type": "before",
+ },
+ "sessionID": "string",
+ },
+ "id": "string",
+ "location": {
+ "directory": "string",
+ },
+ "metadata": {"foo": {}},
+ "model": {
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
+ "outcome": "succeeded",
+ "parentID": "string",
+ "permissions": [
+ {
+ "action": "string",
+ "effect": "allow",
+ "resource": "string",
+ }
+ ],
+ "projectID": "string",
+ "revert": {
+ "files": [
+ {
+ "additions": 0,
+ "deletions": 0,
+ "file": "string",
+ "patch": "string",
+ "status": "added",
+ }
+ ],
+ "messageID": "string",
+ "partID": "string",
+ "snapshot": "string",
+ },
+ "subpath": "string",
+ "time": {
+ "archived": 0,
+ "created": 0,
+ "idle": 0,
+ "updated": 0,
+ "viewed": 0,
+ },
+ "title": "string",
+ "tokens": {
+ "cache": {
+ "read": 0,
+ "write": 0,
+ },
+ "input": 0,
+ "output": 0,
+ "reasoning": 0,
+ },
+ },
messages=[{}],
)
assert_matches_type(SessionInfo, session, path=["response"])
@@ -639,16 +748,142 @@ def test_method_import_(self, client: Opencode) -> None:
@parametrize
def test_method_import__with_all_params(self, client: Opencode) -> None:
session = client.session.import_(
- info={},
+ info={
+ "agent": "string",
+ "cost": 0,
+ "fork": {
+ "boundary": {
+ "messageID": "string",
+ "type": "before",
+ },
+ "sessionID": "string",
+ },
+ "id": "string",
+ "location": {
+ "directory": "string",
+ },
+ "metadata": {"foo": {}},
+ "model": {
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
+ "outcome": "succeeded",
+ "parentID": "string",
+ "permissions": [
+ {
+ "action": "string",
+ "effect": "allow",
+ "resource": "string",
+ }
+ ],
+ "projectID": "string",
+ "revert": {
+ "files": [
+ {
+ "additions": 0,
+ "deletions": 0,
+ "file": "string",
+ "patch": "string",
+ "status": "added",
+ }
+ ],
+ "messageID": "string",
+ "partID": "string",
+ "snapshot": "string",
+ },
+ "subpath": "string",
+ "time": {
+ "archived": 0,
+ "created": 0,
+ "idle": 0,
+ "updated": 0,
+ "viewed": 0,
+ },
+ "title": "string",
+ "tokens": {
+ "cache": {
+ "read": 0,
+ "write": 0,
+ },
+ "input": 0,
+ "output": 0,
+ "reasoning": 0,
+ },
+ },
messages=[{}],
- location={},
+ location={
+ "directory": "string",
+ },
)
assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
def test_raw_response_import_(self, client: Opencode) -> None:
response = client.session.with_raw_response.import_(
- info={},
+ info={
+ "agent": "string",
+ "cost": 0,
+ "fork": {
+ "boundary": {
+ "messageID": "string",
+ "type": "before",
+ },
+ "sessionID": "string",
+ },
+ "id": "string",
+ "location": {
+ "directory": "string",
+ },
+ "metadata": {"foo": {}},
+ "model": {
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
+ "outcome": "succeeded",
+ "parentID": "string",
+ "permissions": [
+ {
+ "action": "string",
+ "effect": "allow",
+ "resource": "string",
+ }
+ ],
+ "projectID": "string",
+ "revert": {
+ "files": [
+ {
+ "additions": 0,
+ "deletions": 0,
+ "file": "string",
+ "patch": "string",
+ "status": "added",
+ }
+ ],
+ "messageID": "string",
+ "partID": "string",
+ "snapshot": "string",
+ },
+ "subpath": "string",
+ "time": {
+ "archived": 0,
+ "created": 0,
+ "idle": 0,
+ "updated": 0,
+ "viewed": 0,
+ },
+ "title": "string",
+ "tokens": {
+ "cache": {
+ "read": 0,
+ "write": 0,
+ },
+ "input": 0,
+ "output": 0,
+ "reasoning": 0,
+ },
+ },
messages=[{}],
)
@@ -660,7 +895,69 @@ def test_raw_response_import_(self, client: Opencode) -> None:
@parametrize
def test_streaming_response_import_(self, client: Opencode) -> None:
with client.session.with_streaming_response.import_(
- info={},
+ info={
+ "agent": "string",
+ "cost": 0,
+ "fork": {
+ "boundary": {
+ "messageID": "string",
+ "type": "before",
+ },
+ "sessionID": "string",
+ },
+ "id": "string",
+ "location": {
+ "directory": "string",
+ },
+ "metadata": {"foo": {}},
+ "model": {
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
+ "outcome": "succeeded",
+ "parentID": "string",
+ "permissions": [
+ {
+ "action": "string",
+ "effect": "allow",
+ "resource": "string",
+ }
+ ],
+ "projectID": "string",
+ "revert": {
+ "files": [
+ {
+ "additions": 0,
+ "deletions": 0,
+ "file": "string",
+ "patch": "string",
+ "status": "added",
+ }
+ ],
+ "messageID": "string",
+ "partID": "string",
+ "snapshot": "string",
+ },
+ "subpath": "string",
+ "time": {
+ "archived": 0,
+ "created": 0,
+ "idle": 0,
+ "updated": 0,
+ "viewed": 0,
+ },
+ "title": "string",
+ "tokens": {
+ "cache": {
+ "read": 0,
+ "write": 0,
+ },
+ "input": 0,
+ "output": 0,
+ "reasoning": 0,
+ },
+ },
messages=[{}],
) as response:
assert not response.is_closed
@@ -728,7 +1025,7 @@ def test_method_log(self, client: Opencode) -> None:
def test_method_log_with_all_params(self, client: Opencode) -> None:
session = client.session.log(
session_id="sessionID",
- after={},
+ after=0,
follow=True,
)
assert session is None
@@ -777,7 +1074,7 @@ def test_method_move_with_all_params(self, client: Opencode) -> None:
session = client.session.move(
session_id="sessionID",
directory="directory",
- delivery={},
+ delivery="steer",
)
assert session is None
@@ -828,13 +1125,42 @@ def test_method_prompt_with_all_params(self, client: Opencode) -> None:
session = client.session.prompt(
session_id="sessionID",
text="text",
- agents=[{}],
- delivery={},
- files=[{}],
- id={},
+ agents=[
+ {
+ "mention": {
+ "end": 0,
+ "start": 0,
+ "text": "string",
+ },
+ "name": "string",
+ }
+ ],
+ delivery="steer",
+ files=[
+ {
+ "description": "string",
+ "mention": {
+ "end": 0,
+ "start": 0,
+ "text": "string",
+ },
+ "name": "string",
+ "uri": "string",
+ }
+ ],
+ id="id",
metadata={"foo": {}},
resume=True,
- skills=[{}],
+ skills=[
+ {
+ "id": "string",
+ "mention": {
+ "end": 0,
+ "start": 0,
+ "text": "string",
+ },
+ }
+ ],
)
assert_matches_type(SessionInboxUser, session, path=["response"])
@@ -923,7 +1249,7 @@ def test_method_shell_with_all_params(self, client: Opencode) -> None:
session = client.session.shell(
session_id="sessionID",
command="command",
- id={},
+ id="id",
)
assert session is None
@@ -1021,7 +1347,7 @@ def test_method_stats(self, client: Opencode) -> None:
def test_method_stats_with_all_params(self, client: Opencode) -> None:
session = client.session.stats(
from_=0,
- project={},
+ project="project",
timezone="timezone",
to=0,
tools="none",
@@ -1094,7 +1420,11 @@ def test_path_params_switch_agent(self, client: Opencode) -> None:
def test_method_switch_model(self, client: Opencode) -> None:
session = client.session.switch_model(
session_id="sessionID",
- model={},
+ model={
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
)
assert session is None
@@ -1102,7 +1432,11 @@ def test_method_switch_model(self, client: Opencode) -> None:
def test_raw_response_switch_model(self, client: Opencode) -> None:
response = client.session.with_raw_response.switch_model(
session_id="sessionID",
- model={},
+ model={
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
)
assert response.is_closed is True
@@ -1114,7 +1448,11 @@ def test_raw_response_switch_model(self, client: Opencode) -> None:
def test_streaming_response_switch_model(self, client: Opencode) -> None:
with client.session.with_streaming_response.switch_model(
session_id="sessionID",
- model={},
+ model={
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -1129,7 +1467,11 @@ def test_path_params_switch_model(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
client.session.with_raw_response.switch_model(
session_id="",
- model={},
+ model={
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
)
@parametrize
@@ -1145,9 +1487,9 @@ def test_method_synthetic_with_all_params(self, client: Opencode) -> None:
session = client.session.synthetic(
session_id="sessionID",
text="text",
- delivery={},
+ delivery="steer",
description="description",
- id={},
+ id="id",
metadata={"foo": {}},
resume=True,
)
@@ -1281,13 +1623,25 @@ async def test_method_create(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.create(
- agent={},
- id={},
- location={},
- metadata={},
- model={},
- parent_id={},
- permissions={},
+ agent="agent",
+ id="id",
+ location={
+ "directory": "string",
+ },
+ metadata={"foo": {}},
+ model={
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
+ parent_id="parentID",
+ permissions=[
+ {
+ "action": "string",
+ "effect": "allow",
+ "resource": "string",
+ }
+ ],
title="title",
)
assert_matches_type(SessionInfo, session, path=["response"])
@@ -1324,8 +1678,14 @@ async def test_method_update_with_all_params(self, async_client: AsyncOpencode)
session = await async_client.session.update(
session_id="sessionID",
archived=True,
- metadata={},
- permissions={},
+ metadata={"foo": {}},
+ permissions=[
+ {
+ "action": "string",
+ "effect": "allow",
+ "resource": "string",
+ }
+ ],
title="title",
)
assert session is None
@@ -1370,14 +1730,14 @@ async def test_method_list(self, async_client: AsyncOpencode) -> None:
async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.list(
archived=True,
- cursor={},
- directory={},
+ cursor="cursor",
+ directory="directory",
limit=0,
order="asc",
- parent_id={},
- project={},
+ parent_id="parentID",
+ project="project",
search="search",
- subpath={},
+ subpath="subpath",
)
assert_matches_type(SessionInfo, session, path=["response"])
@@ -1479,10 +1839,39 @@ async def test_method_command_with_all_params(self, async_client: AsyncOpencode)
session_id="sessionID",
name="name",
text="text",
- agents=[{}],
- delivery={},
- files=[{}],
- skills=[{}],
+ agents=[
+ {
+ "mention": {
+ "end": 0,
+ "start": 0,
+ "text": "string",
+ },
+ "name": "string",
+ }
+ ],
+ delivery="steer",
+ files=[
+ {
+ "description": "string",
+ "mention": {
+ "end": 0,
+ "start": 0,
+ "text": "string",
+ },
+ "name": "string",
+ "uri": "string",
+ }
+ ],
+ skills=[
+ {
+ "id": "string",
+ "mention": {
+ "end": 0,
+ "start": 0,
+ "text": "string",
+ },
+ }
+ ],
)
assert session is None
@@ -1534,8 +1923,8 @@ async def test_method_compact(self, async_client: AsyncOpencode) -> None:
async def test_method_compact_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.compact(
session_id="sessionID",
- delivery={},
- id={},
+ delivery="steer",
+ id="id",
)
assert_matches_type(SessionInboxCompaction, session, path=["response"])
@@ -1620,8 +2009,8 @@ async def test_method_diff_with_all_params(self, async_client: AsyncOpencode) ->
session = await async_client.session.diff(
session_id="sessionID",
context=0,
- from_={},
- to={},
+ from_="from",
+ to="to",
)
assert_matches_type(SessionDiffResponse, session, path=["response"])
@@ -1755,7 +2144,7 @@ async def test_method_fork(self, async_client: AsyncOpencode) -> None:
async def test_method_fork_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.fork(
session_id="sessionID",
- before={},
+ before="before",
)
assert_matches_type(SessionInfo, session, path=["response"])
@@ -1873,7 +2262,69 @@ async def test_path_params_get(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_import_(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.import_(
- info={},
+ info={
+ "agent": "string",
+ "cost": 0,
+ "fork": {
+ "boundary": {
+ "messageID": "string",
+ "type": "before",
+ },
+ "sessionID": "string",
+ },
+ "id": "string",
+ "location": {
+ "directory": "string",
+ },
+ "metadata": {"foo": {}},
+ "model": {
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
+ "outcome": "succeeded",
+ "parentID": "string",
+ "permissions": [
+ {
+ "action": "string",
+ "effect": "allow",
+ "resource": "string",
+ }
+ ],
+ "projectID": "string",
+ "revert": {
+ "files": [
+ {
+ "additions": 0,
+ "deletions": 0,
+ "file": "string",
+ "patch": "string",
+ "status": "added",
+ }
+ ],
+ "messageID": "string",
+ "partID": "string",
+ "snapshot": "string",
+ },
+ "subpath": "string",
+ "time": {
+ "archived": 0,
+ "created": 0,
+ "idle": 0,
+ "updated": 0,
+ "viewed": 0,
+ },
+ "title": "string",
+ "tokens": {
+ "cache": {
+ "read": 0,
+ "write": 0,
+ },
+ "input": 0,
+ "output": 0,
+ "reasoning": 0,
+ },
+ },
messages=[{}],
)
assert_matches_type(SessionInfo, session, path=["response"])
@@ -1881,16 +2332,142 @@ async def test_method_import_(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_import__with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.import_(
- info={},
+ info={
+ "agent": "string",
+ "cost": 0,
+ "fork": {
+ "boundary": {
+ "messageID": "string",
+ "type": "before",
+ },
+ "sessionID": "string",
+ },
+ "id": "string",
+ "location": {
+ "directory": "string",
+ },
+ "metadata": {"foo": {}},
+ "model": {
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
+ "outcome": "succeeded",
+ "parentID": "string",
+ "permissions": [
+ {
+ "action": "string",
+ "effect": "allow",
+ "resource": "string",
+ }
+ ],
+ "projectID": "string",
+ "revert": {
+ "files": [
+ {
+ "additions": 0,
+ "deletions": 0,
+ "file": "string",
+ "patch": "string",
+ "status": "added",
+ }
+ ],
+ "messageID": "string",
+ "partID": "string",
+ "snapshot": "string",
+ },
+ "subpath": "string",
+ "time": {
+ "archived": 0,
+ "created": 0,
+ "idle": 0,
+ "updated": 0,
+ "viewed": 0,
+ },
+ "title": "string",
+ "tokens": {
+ "cache": {
+ "read": 0,
+ "write": 0,
+ },
+ "input": 0,
+ "output": 0,
+ "reasoning": 0,
+ },
+ },
messages=[{}],
- location={},
+ location={
+ "directory": "string",
+ },
)
assert_matches_type(SessionInfo, session, path=["response"])
@parametrize
async def test_raw_response_import_(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.import_(
- info={},
+ info={
+ "agent": "string",
+ "cost": 0,
+ "fork": {
+ "boundary": {
+ "messageID": "string",
+ "type": "before",
+ },
+ "sessionID": "string",
+ },
+ "id": "string",
+ "location": {
+ "directory": "string",
+ },
+ "metadata": {"foo": {}},
+ "model": {
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
+ "outcome": "succeeded",
+ "parentID": "string",
+ "permissions": [
+ {
+ "action": "string",
+ "effect": "allow",
+ "resource": "string",
+ }
+ ],
+ "projectID": "string",
+ "revert": {
+ "files": [
+ {
+ "additions": 0,
+ "deletions": 0,
+ "file": "string",
+ "patch": "string",
+ "status": "added",
+ }
+ ],
+ "messageID": "string",
+ "partID": "string",
+ "snapshot": "string",
+ },
+ "subpath": "string",
+ "time": {
+ "archived": 0,
+ "created": 0,
+ "idle": 0,
+ "updated": 0,
+ "viewed": 0,
+ },
+ "title": "string",
+ "tokens": {
+ "cache": {
+ "read": 0,
+ "write": 0,
+ },
+ "input": 0,
+ "output": 0,
+ "reasoning": 0,
+ },
+ },
messages=[{}],
)
@@ -1902,7 +2479,69 @@ async def test_raw_response_import_(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_streaming_response_import_(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.import_(
- info={},
+ info={
+ "agent": "string",
+ "cost": 0,
+ "fork": {
+ "boundary": {
+ "messageID": "string",
+ "type": "before",
+ },
+ "sessionID": "string",
+ },
+ "id": "string",
+ "location": {
+ "directory": "string",
+ },
+ "metadata": {"foo": {}},
+ "model": {
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
+ "outcome": "succeeded",
+ "parentID": "string",
+ "permissions": [
+ {
+ "action": "string",
+ "effect": "allow",
+ "resource": "string",
+ }
+ ],
+ "projectID": "string",
+ "revert": {
+ "files": [
+ {
+ "additions": 0,
+ "deletions": 0,
+ "file": "string",
+ "patch": "string",
+ "status": "added",
+ }
+ ],
+ "messageID": "string",
+ "partID": "string",
+ "snapshot": "string",
+ },
+ "subpath": "string",
+ "time": {
+ "archived": 0,
+ "created": 0,
+ "idle": 0,
+ "updated": 0,
+ "viewed": 0,
+ },
+ "title": "string",
+ "tokens": {
+ "cache": {
+ "read": 0,
+ "write": 0,
+ },
+ "input": 0,
+ "output": 0,
+ "reasoning": 0,
+ },
+ },
messages=[{}],
) as response:
assert not response.is_closed
@@ -1970,7 +2609,7 @@ async def test_method_log(self, async_client: AsyncOpencode) -> None:
async def test_method_log_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.log(
session_id="sessionID",
- after={},
+ after=0,
follow=True,
)
assert session is None
@@ -2019,7 +2658,7 @@ async def test_method_move_with_all_params(self, async_client: AsyncOpencode) ->
session = await async_client.session.move(
session_id="sessionID",
directory="directory",
- delivery={},
+ delivery="steer",
)
assert session is None
@@ -2070,13 +2709,42 @@ async def test_method_prompt_with_all_params(self, async_client: AsyncOpencode)
session = await async_client.session.prompt(
session_id="sessionID",
text="text",
- agents=[{}],
- delivery={},
- files=[{}],
- id={},
+ agents=[
+ {
+ "mention": {
+ "end": 0,
+ "start": 0,
+ "text": "string",
+ },
+ "name": "string",
+ }
+ ],
+ delivery="steer",
+ files=[
+ {
+ "description": "string",
+ "mention": {
+ "end": 0,
+ "start": 0,
+ "text": "string",
+ },
+ "name": "string",
+ "uri": "string",
+ }
+ ],
+ id="id",
metadata={"foo": {}},
resume=True,
- skills=[{}],
+ skills=[
+ {
+ "id": "string",
+ "mention": {
+ "end": 0,
+ "start": 0,
+ "text": "string",
+ },
+ }
+ ],
)
assert_matches_type(SessionInboxUser, session, path=["response"])
@@ -2165,7 +2833,7 @@ async def test_method_shell_with_all_params(self, async_client: AsyncOpencode) -
session = await async_client.session.shell(
session_id="sessionID",
command="command",
- id={},
+ id="id",
)
assert session is None
@@ -2263,7 +2931,7 @@ async def test_method_stats(self, async_client: AsyncOpencode) -> None:
async def test_method_stats_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.stats(
from_=0,
- project={},
+ project="project",
timezone="timezone",
to=0,
tools="none",
@@ -2336,7 +3004,11 @@ async def test_path_params_switch_agent(self, async_client: AsyncOpencode) -> No
async def test_method_switch_model(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.switch_model(
session_id="sessionID",
- model={},
+ model={
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
)
assert session is None
@@ -2344,7 +3016,11 @@ async def test_method_switch_model(self, async_client: AsyncOpencode) -> None:
async def test_raw_response_switch_model(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.switch_model(
session_id="sessionID",
- model={},
+ model={
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
)
assert response.is_closed is True
@@ -2356,7 +3032,11 @@ async def test_raw_response_switch_model(self, async_client: AsyncOpencode) -> N
async def test_streaming_response_switch_model(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.switch_model(
session_id="sessionID",
- model={},
+ model={
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -2371,7 +3051,11 @@ async def test_path_params_switch_model(self, async_client: AsyncOpencode) -> No
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
await async_client.session.with_raw_response.switch_model(
session_id="",
- model={},
+ model={
+ "id": "string",
+ "providerID": "string",
+ "variant": "string",
+ },
)
@parametrize
@@ -2387,9 +3071,9 @@ async def test_method_synthetic_with_all_params(self, async_client: AsyncOpencod
session = await async_client.session.synthetic(
session_id="sessionID",
text="text",
- delivery={},
+ delivery="steer",
description="description",
- id={},
+ id="id",
metadata={"foo": {}},
resume=True,
)
diff --git a/tests/api_resources/test_voice.py b/tests/api_resources/test_voice.py
index 1248a0a..bc1a29e 100644
--- a/tests/api_resources/test_voice.py
+++ b/tests/api_resources/test_voice.py
@@ -21,7 +21,11 @@ class TestVoice:
def test_method_session(self, client: Opencode) -> None:
voice = client.voice.session(
model="model",
- transport={},
+ transport={
+ "dcid": 0,
+ "sdp": "string",
+ "type": "webrtc",
+ },
)
assert_matches_type(VoiceSessionResponse, voice, path=["response"])
@@ -29,7 +33,11 @@ def test_method_session(self, client: Opencode) -> None:
def test_method_session_with_all_params(self, client: Opencode) -> None:
voice = client.voice.session(
model="model",
- transport={},
+ transport={
+ "dcid": 0,
+ "sdp": "string",
+ "type": "webrtc",
+ },
session={"foo": {}},
)
assert_matches_type(VoiceSessionResponse, voice, path=["response"])
@@ -38,7 +46,11 @@ def test_method_session_with_all_params(self, client: Opencode) -> None:
def test_raw_response_session(self, client: Opencode) -> None:
response = client.voice.with_raw_response.session(
model="model",
- transport={},
+ transport={
+ "dcid": 0,
+ "sdp": "string",
+ "type": "webrtc",
+ },
)
assert response.is_closed is True
@@ -50,7 +62,11 @@ def test_raw_response_session(self, client: Opencode) -> None:
def test_streaming_response_session(self, client: Opencode) -> None:
with client.voice.with_streaming_response.session(
model="model",
- transport={},
+ transport={
+ "dcid": 0,
+ "sdp": "string",
+ "type": "webrtc",
+ },
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -70,7 +86,11 @@ class TestAsyncVoice:
async def test_method_session(self, async_client: AsyncOpencode) -> None:
voice = await async_client.voice.session(
model="model",
- transport={},
+ transport={
+ "dcid": 0,
+ "sdp": "string",
+ "type": "webrtc",
+ },
)
assert_matches_type(VoiceSessionResponse, voice, path=["response"])
@@ -78,7 +98,11 @@ async def test_method_session(self, async_client: AsyncOpencode) -> None:
async def test_method_session_with_all_params(self, async_client: AsyncOpencode) -> None:
voice = await async_client.voice.session(
model="model",
- transport={},
+ transport={
+ "dcid": 0,
+ "sdp": "string",
+ "type": "webrtc",
+ },
session={"foo": {}},
)
assert_matches_type(VoiceSessionResponse, voice, path=["response"])
@@ -87,7 +111,11 @@ async def test_method_session_with_all_params(self, async_client: AsyncOpencode)
async def test_raw_response_session(self, async_client: AsyncOpencode) -> None:
response = await async_client.voice.with_raw_response.session(
model="model",
- transport={},
+ transport={
+ "dcid": 0,
+ "sdp": "string",
+ "type": "webrtc",
+ },
)
assert response.is_closed is True
@@ -99,7 +127,11 @@ async def test_raw_response_session(self, async_client: AsyncOpencode) -> None:
async def test_streaming_response_session(self, async_client: AsyncOpencode) -> None:
async with async_client.voice.with_streaming_response.session(
model="model",
- transport={},
+ transport={
+ "dcid": 0,
+ "sdp": "string",
+ "type": "webrtc",
+ },
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
From 04f94a35a474c178d28ba43f9654acaff736f3ac Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 17:25:35 +0530
Subject: [PATCH 16/41] chore: regenerate SDK
Ferrule-Generated-From: c360b498ec86c867c2df3d0bc9195039fa7e7bd8
---
ferrule.lock | 6 +++---
tests/api_resources/test_event.py | 28 ++++++++++++----------------
tests/api_resources/test_message.py | 17 +++++++++--------
tests/api_resources/test_session.py | 17 +++++++++--------
4 files changed, 33 insertions(+), 35 deletions(-)
diff --git a/ferrule.lock b/ferrule.lock
index c165cfa..fe1ddb3 100644
--- a/ferrule.lock
+++ b/ferrule.lock
@@ -786,14 +786,14 @@
"tests/api_resources/test_command.py": "8be3e96e7ff2ad6750fc1085ce6d422ca8e1cda3b2443dca2de0953c8f9edee5",
"tests/api_resources/test_config.py": "3b646f5206a3143c00ad183ba3a1f7cbc0a588058e8ab9751d316609cd5da218",
"tests/api_resources/test_credential.py": "3158903c567e31b5938f1efdab5c8a214284eafe1a46059289c3216e8c74eb26",
- "tests/api_resources/test_event.py": "dea5556b0932db3ed178262dd925e3aeeda69783ebf313de0b6c20dd2f600205",
+ "tests/api_resources/test_event.py": "49bfe90f1ace1b9808391d06f4b5f04528873414d05df5f1fdede2543af6d0e0",
"tests/api_resources/test_file.py": "6b62a1a08c9a76d179e5829e530986df09586373a747986667edda47f4977e33",
"tests/api_resources/test_form.py": "35fdd2d079242efe0a6c26c06198910ed89a2cc1421e5f74509dd202fd830ae5",
"tests/api_resources/test_generate.py": "f4156c880368a6bcfce1b68220835fdfe17e9704f874a783be46397b71b69bf6",
"tests/api_resources/test_integration.py": "e2ae3ea7fcff53120d0ee3c990f6bc7517e281cd03909e3f74c33cf535269bcc",
"tests/api_resources/test_location.py": "40f6986acf63ed7e0e279c9df41e970405ac2474f95928f28112921d8d16f13d",
"tests/api_resources/test_mcp.py": "aa097634cc812da7210cbbfd3364faf2e4abf7ed6df03a45240a50e3e194b077",
- "tests/api_resources/test_message.py": "797e28f5b7d53858478f346ccb89051c48fe6134952a409461f897ae4adaf74e",
+ "tests/api_resources/test_message.py": "60e274837f502bad62c52cf1300639b7d137279cfb0dae49a2268abde4cac6b7",
"tests/api_resources/test_model.py": "afd0aa11174a17f25cb7a060597d913202359074c20676ee8b0dabed853548bd",
"tests/api_resources/test_permission.py": "ff26711b38971fdc8833432996b19d821a9f1f9de4b973f239e7eb35c44da2af",
"tests/api_resources/test_plugin.py": "2685fa322627066f274e068959165468524db459aa47684976f57e13f35bb05b",
@@ -803,7 +803,7 @@
"tests/api_resources/test_reference.py": "82661f6cecc6f0fe44d63fd1180817b27869845578f5d71d039916ee1679b8ec",
"tests/api_resources/test_rpc.py": "1dabbd8dbdab2902c05721ea5c344bd57f4c4e5c85af15c9ff60dd650baf4907",
"tests/api_resources/test_server.py": "bf49f7693308b7ede9fd1cf2e4e555dce88ec560c6e908393020e3ebc7fac9db",
- "tests/api_resources/test_session.py": "c6d85bd4dbe8a37207c1ac6a6e449bc2456a145f3461e68dd39472426973bcab",
+ "tests/api_resources/test_session.py": "b23fbb1eecf734b6e5bfdfed33b8a570e45ef1f3a4ee84dfd73357658442d407",
"tests/api_resources/test_shell.py": "544c8e14854989a874fc1e6e5ca9067ccae4bfd44f81ea8a845fa101176181d4",
"tests/api_resources/test_skill.py": "cf22c66273943ec809067cfac8d6b9d14f71e93f862b1be615ae2c51933c34cf",
"tests/api_resources/test_vcs.py": "502249bc039765b74d0860cc151f48f1cc5f2ad4456d123b2c950253411e6bb1",
diff --git a/tests/api_resources/test_event.py b/tests/api_resources/test_event.py
index 1a9595d..0d06cb4 100644
--- a/tests/api_resources/test_event.py
+++ b/tests/api_resources/test_event.py
@@ -8,8 +8,6 @@
import pytest
from opencode import Opencode, AsyncOpencode
-from tests.utils import assert_matches_type
-from opencode.types.shared import Event
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -19,17 +17,16 @@ class TestEvent:
@parametrize
def test_method_subscribe(self, client: Opencode) -> None:
- event = client.event.subscribe()
- assert_matches_type(Event, event, path=["response"])
+ event_stream = client.event.subscribe()
+ event_stream.response.close()
@parametrize
def test_raw_response_subscribe(self, client: Opencode) -> None:
response = client.event.with_raw_response.subscribe()
- assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- event = response.parse()
- assert_matches_type(Event, event, path=["response"])
+ stream = response.parse()
+ stream.close()
@parametrize
def test_streaming_response_subscribe(self, client: Opencode) -> None:
@@ -37,8 +34,8 @@ def test_streaming_response_subscribe(self, client: Opencode) -> None:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- event = response.parse()
- assert_matches_type(Event, event, path=["response"])
+ stream = response.parse()
+ stream.close()
assert cast(Any, response.is_closed) is True
@@ -50,17 +47,16 @@ class TestAsyncEvent:
@parametrize
async def test_method_subscribe(self, async_client: AsyncOpencode) -> None:
- event = await async_client.event.subscribe()
- assert_matches_type(Event, event, path=["response"])
+ event_stream = await async_client.event.subscribe()
+ await event_stream.response.aclose()
@parametrize
async def test_raw_response_subscribe(self, async_client: AsyncOpencode) -> None:
response = await async_client.event.with_raw_response.subscribe()
- assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- event = await response.parse()
- assert_matches_type(Event, event, path=["response"])
+ stream = await response.parse()
+ await stream.close()
@parametrize
async def test_streaming_response_subscribe(self, async_client: AsyncOpencode) -> None:
@@ -68,7 +64,7 @@ async def test_streaming_response_subscribe(self, async_client: AsyncOpencode) -
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
- event = await response.parse()
- assert_matches_type(Event, event, path=["response"])
+ stream = await response.parse()
+ await stream.close()
assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_message.py b/tests/api_resources/test_message.py
index f68869c..44f71ea 100644
--- a/tests/api_resources/test_message.py
+++ b/tests/api_resources/test_message.py
@@ -9,6 +9,7 @@
from opencode import Opencode, AsyncOpencode
from tests.utils import assert_matches_type
+from opencode.pagination import SyncCursorPage, AsyncCursorPage
from opencode.types.shared import SessionMessageInfo
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -22,7 +23,7 @@ def test_method_list(self, client: Opencode) -> None:
message = client.message.list(
session_id="sessionID",
)
- assert_matches_type(SessionMessageInfo, message, path=["response"])
+ assert_matches_type(SyncCursorPage[SessionMessageInfo], message, path=["response"])
@parametrize
def test_method_list_with_all_params(self, client: Opencode) -> None:
@@ -33,7 +34,7 @@ def test_method_list_with_all_params(self, client: Opencode) -> None:
order="asc",
type="agent-switched",
)
- assert_matches_type(SessionMessageInfo, message, path=["response"])
+ assert_matches_type(SyncCursorPage[SessionMessageInfo], message, path=["response"])
@parametrize
def test_raw_response_list(self, client: Opencode) -> None:
@@ -44,7 +45,7 @@ def test_raw_response_list(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
message = response.parse()
- assert_matches_type(SessionMessageInfo, message, path=["response"])
+ assert_matches_type(SyncCursorPage[SessionMessageInfo], message, path=["response"])
@parametrize
def test_streaming_response_list(self, client: Opencode) -> None:
@@ -55,7 +56,7 @@ def test_streaming_response_list(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
message = response.parse()
- assert_matches_type(SessionMessageInfo, message, path=["response"])
+ assert_matches_type(SyncCursorPage[SessionMessageInfo], message, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -77,7 +78,7 @@ async def test_method_list(self, async_client: AsyncOpencode) -> None:
message = await async_client.message.list(
session_id="sessionID",
)
- assert_matches_type(SessionMessageInfo, message, path=["response"])
+ assert_matches_type(AsyncCursorPage[SessionMessageInfo], message, path=["response"])
@parametrize
async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
@@ -88,7 +89,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) ->
order="asc",
type="agent-switched",
)
- assert_matches_type(SessionMessageInfo, message, path=["response"])
+ assert_matches_type(AsyncCursorPage[SessionMessageInfo], message, path=["response"])
@parametrize
async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
@@ -99,7 +100,7 @@ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
message = await response.parse()
- assert_matches_type(SessionMessageInfo, message, path=["response"])
+ assert_matches_type(AsyncCursorPage[SessionMessageInfo], message, path=["response"])
@parametrize
async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
@@ -110,7 +111,7 @@ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> Non
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
message = await response.parse()
- assert_matches_type(SessionMessageInfo, message, path=["response"])
+ assert_matches_type(AsyncCursorPage[SessionMessageInfo], message, path=["response"])
assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_session.py b/tests/api_resources/test_session.py
index 16ab022..85394f4 100644
--- a/tests/api_resources/test_session.py
+++ b/tests/api_resources/test_session.py
@@ -15,6 +15,7 @@
SessionContextResponse,
SessionGenerateResponse,
)
+from opencode.pagination import SyncCursorPage, AsyncCursorPage
from opencode.types.shared import (
SessionInfo,
SessionInboxUser,
@@ -140,7 +141,7 @@ def test_path_params_update(self, client: Opencode) -> None:
@parametrize
def test_method_list(self, client: Opencode) -> None:
session = client.session.list()
- assert_matches_type(SessionInfo, session, path=["response"])
+ assert_matches_type(SyncCursorPage[SessionInfo], session, path=["response"])
@parametrize
def test_method_list_with_all_params(self, client: Opencode) -> None:
@@ -155,7 +156,7 @@ def test_method_list_with_all_params(self, client: Opencode) -> None:
search="search",
subpath="subpath",
)
- assert_matches_type(SessionInfo, session, path=["response"])
+ assert_matches_type(SyncCursorPage[SessionInfo], session, path=["response"])
@parametrize
def test_raw_response_list(self, client: Opencode) -> None:
@@ -164,7 +165,7 @@ def test_raw_response_list(self, client: Opencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionInfo, session, path=["response"])
+ assert_matches_type(SyncCursorPage[SessionInfo], session, path=["response"])
@parametrize
def test_streaming_response_list(self, client: Opencode) -> None:
@@ -173,7 +174,7 @@ def test_streaming_response_list(self, client: Opencode) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = response.parse()
- assert_matches_type(SessionInfo, session, path=["response"])
+ assert_matches_type(SyncCursorPage[SessionInfo], session, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -1724,7 +1725,7 @@ async def test_path_params_update(self, async_client: AsyncOpencode) -> None:
@parametrize
async def test_method_list(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.list()
- assert_matches_type(SessionInfo, session, path=["response"])
+ assert_matches_type(AsyncCursorPage[SessionInfo], session, path=["response"])
@parametrize
async def test_method_list_with_all_params(self, async_client: AsyncOpencode) -> None:
@@ -1739,7 +1740,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncOpencode) ->
search="search",
subpath="subpath",
)
- assert_matches_type(SessionInfo, session, path=["response"])
+ assert_matches_type(AsyncCursorPage[SessionInfo], session, path=["response"])
@parametrize
async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
@@ -1748,7 +1749,7 @@ async def test_raw_response_list(self, async_client: AsyncOpencode) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionInfo, session, path=["response"])
+ assert_matches_type(AsyncCursorPage[SessionInfo], session, path=["response"])
@parametrize
async def test_streaming_response_list(self, async_client: AsyncOpencode) -> None:
@@ -1757,7 +1758,7 @@ async def test_streaming_response_list(self, async_client: AsyncOpencode) -> Non
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
session = await response.parse()
- assert_matches_type(SessionInfo, session, path=["response"])
+ assert_matches_type(AsyncCursorPage[SessionInfo], session, path=["response"])
assert cast(Any, response.is_closed) is True
From 29fa03dd93373b7722e77eddb2cb6c157dd2ed7c Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 17:39:23 +0530
Subject: [PATCH 17/41] Skip the tests the mock server cannot serve
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
stainless.yml | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/stainless.yml b/stainless.yml
index df40228..065e16c 100644
--- a/stainless.yml
+++ b/stainless.yml
@@ -584,6 +584,7 @@ resources:
read:
endpoint: get /api/experimental/session/{sessionID}/terminal/read
unwrap_response: data
+ skip_test_reason: "The mock server rejects integer query parameters typed through a $ref in anyOf."
list:
endpoint: get /api/experimental/session/{sessionID}/terminal
unwrap_response: data
@@ -780,7 +781,9 @@ resources:
generate:
endpoint: post /api/session/{sessionID}/generate
unwrap_response: data
- log: get /api/experimental/session/{sessionID}/log
+ log:
+ endpoint: get /api/experimental/session/{sessionID}/log
+ skip_test_reason: "The mock server rejects integer query parameters typed through a $ref in anyOf."
interrupt: post /api/session/{sessionID}/interrupt
background: post /api/session/{sessionID}/background
environment: put /api/session/{sessionID}/environment
From 3368901f325a7cae0093f2cd0bad2be4eb69b3b1 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 17:40:36 +0530
Subject: [PATCH 18/41] chore: regenerate SDK
Ferrule-Generated-From: 7cb4295b7673044201dc6232e7b26cdf7b5df50a
---
ferrule.lock | 8 ++++----
.../types/shared/session_inbox_compaction_payload.py | 6 ++----
.../api_resources/experimental/test_persistent_pty.py | 10 ++++++++++
tests/api_resources/test_session.py | 10 ++++++++++
4 files changed, 26 insertions(+), 8 deletions(-)
diff --git a/ferrule.lock b/ferrule.lock
index fe1ddb3..7ab2580 100644
--- a/ferrule.lock
+++ b/ferrule.lock
@@ -1,5 +1,5 @@
{
- "config_sha256": "4e800af8877cf4c5cb8c662b42cadbf61a9a696ff26f88c525a499ac3547344f",
+ "config_sha256": "13d15125c26cc8e7c7ccc11f57dcfc79a8455f50d969516b379b9b813222077c",
"ferrule_version": "0.1.0",
"files": {
".github/workflows/ci.yml": "5ac1be1c103477e7dcdb28d276ff14fb4ebf92650e92d6912e605a64df2dd27b",
@@ -507,7 +507,7 @@
"src/opencode/types/shared/session_idle_event.py": "d0315d5011638cbf5d47c0b7d4bb69ed7e5591491c07dcf2f8f788100f6824ce",
"src/opencode/types/shared/session_inbox_cancelled_event.py": "617d88fd62f6cd02114844d4247ca01279f71397dd60c7a7f05d8e2b4069574d",
"src/opencode/types/shared/session_inbox_compaction.py": "4a761a4e85a6af46ec1a0476889114d4c1ae74fc741dd76aadf2f31d00396037",
- "src/opencode/types/shared/session_inbox_compaction_payload.py": "084fdbfecc8ebae043e8c3c814ac035e1f609833105aa875f445fe14ffff2a71",
+ "src/opencode/types/shared/session_inbox_compaction_payload.py": "68383075b39b302ab1c6df5579566c5af77b6a466665c4e770ffc08fd3435ae0",
"src/opencode/types/shared/session_inbox_delivered_event.py": "2cc752332fd10653bb37a93c6ccd079795782103ee06d80370149f77efdbf90a",
"src/opencode/types/shared/session_inbox_delivery.py": "4dae712d09bd0dc3f42769f66153d9d9a8625c9a054ed88fd33259aa3cb5b024",
"src/opencode/types/shared/session_inbox_delivery_changed_event.py": "c37b6970ffb7c576ffab20c2da176d8d3aa174a1d57a04686c8622ea4d6d4386",
@@ -760,7 +760,7 @@
"tests/api_resources/debug/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/debug/test_location.py": "8dc2ed87566aac5a4c6412b90be8ab49ad2049c5f18cfd966efb8158750fe73d",
"tests/api_resources/experimental/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
- "tests/api_resources/experimental/test_persistent_pty.py": "effd322779465701098e582876b1d2025b4020f7b338c31812d6f6d72672c47b",
+ "tests/api_resources/experimental/test_persistent_pty.py": "49fc5e73f7d1e6c5d57da9a71673c2105bb93135e3b438aa635354485684cbd1",
"tests/api_resources/integration/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/integration/test_command.py": "7f54f3b02479958b7e94085b1bf560f6297849289ba03582945f7a69b82e5c3b",
"tests/api_resources/integration/test_connect.py": "286b94346bc5d6b56dda4e6ab5fd20e02be8b9549506f5e1b524f58b40a25ad6",
@@ -803,7 +803,7 @@
"tests/api_resources/test_reference.py": "82661f6cecc6f0fe44d63fd1180817b27869845578f5d71d039916ee1679b8ec",
"tests/api_resources/test_rpc.py": "1dabbd8dbdab2902c05721ea5c344bd57f4c4e5c85af15c9ff60dd650baf4907",
"tests/api_resources/test_server.py": "bf49f7693308b7ede9fd1cf2e4e555dce88ec560c6e908393020e3ebc7fac9db",
- "tests/api_resources/test_session.py": "b23fbb1eecf734b6e5bfdfed33b8a570e45ef1f3a4ee84dfd73357658442d407",
+ "tests/api_resources/test_session.py": "86d5ae01518ac3fc53750d397c0d113c6c8f28b39271491684241a6c5ca40d03",
"tests/api_resources/test_shell.py": "544c8e14854989a874fc1e6e5ca9067ccae4bfd44f81ea8a845fa101176181d4",
"tests/api_resources/test_skill.py": "cf22c66273943ec809067cfac8d6b9d14f71e93f862b1be615ae2c51933c34cf",
"tests/api_resources/test_vcs.py": "502249bc039765b74d0860cc151f48f1cc5f2ad4456d123b2c950253411e6bb1",
diff --git a/src/opencode/types/shared/session_inbox_compaction_payload.py b/src/opencode/types/shared/session_inbox_compaction_payload.py
index 13361b7..c378e68 100644
--- a/src/opencode/types/shared/session_inbox_compaction_payload.py
+++ b/src/opencode/types/shared/session_inbox_compaction_payload.py
@@ -1,9 +1,7 @@
# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-from ..._models import BaseModel
+from typing_extensions import TypeAlias
__all__ = ["SessionInboxCompactionPayload"]
-
-class SessionInboxCompactionPayload(BaseModel):
- pass
+SessionInboxCompactionPayload: TypeAlias = object
diff --git a/tests/api_resources/experimental/test_persistent_pty.py b/tests/api_resources/experimental/test_persistent_pty.py
index b0a18c0..63e570d 100644
--- a/tests/api_resources/experimental/test_persistent_pty.py
+++ b/tests/api_resources/experimental/test_persistent_pty.py
@@ -305,6 +305,7 @@ def test_streaming_response_handoff(self, client: Opencode) -> None:
assert cast(Any, response.is_closed) is True
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
def test_method_read(self, client: Opencode) -> None:
persistent_pty = client.experimental.persistent_pty.read(
@@ -312,6 +313,7 @@ def test_method_read(self, client: Opencode) -> None:
)
assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
def test_method_read_with_all_params(self, client: Opencode) -> None:
persistent_pty = client.experimental.persistent_pty.read(
@@ -320,6 +322,7 @@ def test_method_read_with_all_params(self, client: Opencode) -> None:
)
assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
def test_raw_response_read(self, client: Opencode) -> None:
response = client.experimental.persistent_pty.with_raw_response.read(
@@ -331,6 +334,7 @@ def test_raw_response_read(self, client: Opencode) -> None:
persistent_pty = response.parse()
assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
def test_streaming_response_read(self, client: Opencode) -> None:
with client.experimental.persistent_pty.with_streaming_response.read(
@@ -344,6 +348,7 @@ def test_streaming_response_read(self, client: Opencode) -> None:
assert cast(Any, response.is_closed) is True
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
def test_path_params_read(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
@@ -737,6 +742,7 @@ async def test_streaming_response_handoff(self, async_client: AsyncOpencode) ->
assert cast(Any, response.is_closed) is True
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
async def test_method_read(self, async_client: AsyncOpencode) -> None:
persistent_pty = await async_client.experimental.persistent_pty.read(
@@ -744,6 +750,7 @@ async def test_method_read(self, async_client: AsyncOpencode) -> None:
)
assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
async def test_method_read_with_all_params(self, async_client: AsyncOpencode) -> None:
persistent_pty = await async_client.experimental.persistent_pty.read(
@@ -752,6 +759,7 @@ async def test_method_read_with_all_params(self, async_client: AsyncOpencode) ->
)
assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
async def test_raw_response_read(self, async_client: AsyncOpencode) -> None:
response = await async_client.experimental.persistent_pty.with_raw_response.read(
@@ -763,6 +771,7 @@ async def test_raw_response_read(self, async_client: AsyncOpencode) -> None:
persistent_pty = await response.parse()
assert_matches_type(PersistentPtyReadResponse, persistent_pty, path=["response"])
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
async def test_streaming_response_read(self, async_client: AsyncOpencode) -> None:
async with async_client.experimental.persistent_pty.with_streaming_response.read(
@@ -776,6 +785,7 @@ async def test_streaming_response_read(self, async_client: AsyncOpencode) -> Non
assert cast(Any, response.is_closed) is True
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
async def test_path_params_read(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
diff --git a/tests/api_resources/test_session.py b/tests/api_resources/test_session.py
index 85394f4..5bb431c 100644
--- a/tests/api_resources/test_session.py
+++ b/tests/api_resources/test_session.py
@@ -1015,6 +1015,7 @@ def test_path_params_interrupt(self, client: Opencode) -> None:
session_id="",
)
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
def test_method_log(self, client: Opencode) -> None:
session = client.session.log(
@@ -1022,6 +1023,7 @@ def test_method_log(self, client: Opencode) -> None:
)
assert session is None
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
def test_method_log_with_all_params(self, client: Opencode) -> None:
session = client.session.log(
@@ -1031,6 +1033,7 @@ def test_method_log_with_all_params(self, client: Opencode) -> None:
)
assert session is None
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
def test_raw_response_log(self, client: Opencode) -> None:
response = client.session.with_raw_response.log(
@@ -1042,6 +1045,7 @@ def test_raw_response_log(self, client: Opencode) -> None:
session = response.parse()
assert session is None
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
def test_streaming_response_log(self, client: Opencode) -> None:
with client.session.with_streaming_response.log(
@@ -1055,6 +1059,7 @@ def test_streaming_response_log(self, client: Opencode) -> None:
assert cast(Any, response.is_closed) is True
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
def test_path_params_log(self, client: Opencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
@@ -2599,6 +2604,7 @@ async def test_path_params_interrupt(self, async_client: AsyncOpencode) -> None:
session_id="",
)
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
async def test_method_log(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.log(
@@ -2606,6 +2612,7 @@ async def test_method_log(self, async_client: AsyncOpencode) -> None:
)
assert session is None
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
async def test_method_log_with_all_params(self, async_client: AsyncOpencode) -> None:
session = await async_client.session.log(
@@ -2615,6 +2622,7 @@ async def test_method_log_with_all_params(self, async_client: AsyncOpencode) ->
)
assert session is None
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
async def test_raw_response_log(self, async_client: AsyncOpencode) -> None:
response = await async_client.session.with_raw_response.log(
@@ -2626,6 +2634,7 @@ async def test_raw_response_log(self, async_client: AsyncOpencode) -> None:
session = await response.parse()
assert session is None
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
async def test_streaming_response_log(self, async_client: AsyncOpencode) -> None:
async with async_client.session.with_streaming_response.log(
@@ -2639,6 +2648,7 @@ async def test_streaming_response_log(self, async_client: AsyncOpencode) -> None
assert cast(Any, response.is_closed) is True
+ @pytest.mark.skip(reason="The mock server rejects integer query parameters typed through a $ref in anyOf.")
@parametrize
async def test_path_params_log(self, async_client: AsyncOpencode) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `session_id` but received ''"):
From 62b701e494a34dedbf18cfe0cc78e9bf2f362add Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 17:41:10 +0530
Subject: [PATCH 19/41] Keep the SDK's own CI out of generation
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
.github/workflows/ci.yml | 156 +++++++++++++++------------
.github/workflows/publish-pypi.yml | 31 ------
.github/workflows/release-doctor.yml | 21 ----
stainless.yml | 4 +
4 files changed, 93 insertions(+), 119 deletions(-)
delete mode 100644 .github/workflows/publish-pypi.yml
delete mode 100644 .github/workflows/release-doctor.yml
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index dab74f3..8d64a6f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,85 +1,107 @@
name: CI
+
on:
- push:
- branches:
- - '**'
- - '!integrated/**'
- - '!stl-preview-head/**'
- - '!stl-preview-base/**'
- - '!generated'
- - '!codegen/**'
- - 'codegen/stl/**'
pull_request:
- branches-ignore:
- - 'stl-preview-head/**'
- - 'stl-preview-base/**'
+ workflow_dispatch:
+ workflow_call: # the release workflow runs CI before publishing
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
jobs:
lint:
- timeout-minutes: 10
- name: lint
+ name: Lint, types, generated code
runs-on: ubuntu-latest
- if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
+ with:
+ enable-cache: true
+ - run: uv sync --locked
+ - run: uv run ruff check .
+ - run: uv run ruff format --check .
+ - run: uv run pyright
+ - run: uv run mypy
+ - name: Generated code matches spec/contract.json
+ run: uv run python -m codegen --check
- - name: Install Rye
- run: |
- curl -sSf https://rye.astral.sh/get | bash
- echo "$HOME/.rye/shims" >> $GITHUB_PATH
- env:
- RYE_VERSION: '0.44.0'
- RYE_INSTALL_OPTION: '--yes'
+ test:
+ name: Test (Python ${{ matrix.python }}, ${{ matrix.os }})
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest]
+ python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
+ include:
+ - os: macos-latest
+ python: "3.13"
+ - os: windows-latest
+ python: "3.13"
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
+ with:
+ enable-cache: true
+ python-version: ${{ matrix.python }}
+ - run: uv sync --locked
+ - run: uv run pytest -m "not live" --cov --cov-report=term-missing
- - name: Install dependencies
- run: rye sync --all-features
+ lowest:
+ name: Test with lowest supported dependencies
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
+ with:
+ python-version: "3.10"
+ - run: >-
+ uv run --isolated --resolution lowest-direct --with-editable '.[pty,aiohttp]'
+ --with pytest --with respx --with trio
+ pytest -m "not live" -p no:cacheprovider
- - name: Run lints
- run: ./scripts/lint
+ live:
+ name: Live server
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
+ with:
+ enable-cache: true
+ - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
+ with:
+ node-version: 22
+ # The OpenCode version is pinned by hash in .github/live/package-lock.json.
+ - run: npm ci --prefix .github/live
+ - run: echo "$GITHUB_WORKSPACE/.github/live/node_modules/.bin" >> "$GITHUB_PATH"
+ - run: opencode --version
+ - run: uv sync --locked
+ - run: uv run pytest -m live -v
build:
- if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
- timeout-minutes: 10
- name: build
- permissions:
- contents: read
- id-token: write
+ name: Build distributions
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
-
- - name: Install Rye
- run: |
- curl -sSf https://rye.astral.sh/get | bash
- echo "$HOME/.rye/shims" >> $GITHUB_PATH
- env:
- RYE_VERSION: '0.44.0'
- RYE_INSTALL_OPTION: '--yes'
-
- - name: Install dependencies
- run: rye sync --all-features
-
- - name: Run build
- run: rye build
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
+ - run: uv build
+ - run: uvx twine check --strict dist/*
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
+ with:
+ name: dist
+ path: dist/
- test:
- timeout-minutes: 10
- name: test
+ fuzz:
+ name: Fuzz SSE decoder
runs-on: ubuntu-latest
- if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
-
- - name: Install Rye
- run: |
- curl -sSf https://rye.astral.sh/get | bash
- echo "$HOME/.rye/shims" >> $GITHUB_PATH
- env:
- RYE_VERSION: '0.44.0'
- RYE_INSTALL_OPTION: '--yes'
-
- - name: Bootstrap
- run: ./scripts/bootstrap
-
- - name: Run tests
- run: ./scripts/test
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
+ with:
+ python-version: "3.12"
+ - run: uv sync --locked
+ - run: uv run --with atheris python fuzz/fuzz_sse.py -max_total_time=60
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
deleted file mode 100644
index 52fc2f8..0000000
--- a/.github/workflows/publish-pypi.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-# This workflow is triggered when a GitHub release is created.
-# It can also be run manually to re-publish to PyPI in case it failed for some reason.
-# You can run this workflow by navigating to https://www.github.com//actions/workflows/publish-pypi.yml
-name: Publish PyPI
-on:
- workflow_dispatch:
-
- release:
- types: [published]
-
-jobs:
- publish:
- name: publish
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
-
- - name: Install Rye
- run: |
- curl -sSf https://rye.astral.sh/get | bash
- echo "$HOME/.rye/shims" >> $GITHUB_PATH
- env:
- RYE_VERSION: '0.44.0'
- RYE_INSTALL_OPTION: '--yes'
-
- - name: Publish to PyPI
- run: |
- bash ./bin/publish-pypi
- env:
- PYPI_TOKEN: ${{ secrets.OPENCODE_PYPI_TOKEN || secrets.PYPI_TOKEN }}
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
deleted file mode 100644
index 2047f81..0000000
--- a/.github/workflows/release-doctor.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-name: Release Doctor
-on:
- pull_request:
- branches:
- - main
- workflow_dispatch:
-
-jobs:
- release_doctor:
- name: release doctor
- runs-on: ubuntu-latest
- if: github.repository == '' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
-
- steps:
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
-
- - name: Check release environment
- run: |
- bash ./bin/check-release-environment
- env:
- PYPI_TOKEN: ${{ secrets.OPENCODE_PYPI_TOKEN || secrets.PYPI_TOKEN }}
diff --git a/stainless.yml b/stainless.yml
index 065e16c..b1938b4 100644
--- a/stainless.yml
+++ b/stainless.yml
@@ -79,6 +79,10 @@ targets:
keep_files:
- .devcontainer/Dockerfile
- .devcontainer/devcontainer.json
+ # The SDK's own CI (uv) and release workflow; not the generated rye ones.
+ - .github/workflows/ci.yml
+ - .github/workflows/publish-pypi.yml
+ - .github/workflows/release-doctor.yml
- .gitignore
- .python-version
- .release-please-manifest.json
From 9776f7f720469f812b3e036331ee4bc8da29a844 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 17:45:14 +0530
Subject: [PATCH 20/41] Remove the former generator's modules; keep
codegen/extract for the contract
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
codegen/__init__.py | 0
codegen/__main__.py | 156 -
codegen/naming.py | 67 -
codegen/operations.py | 220 -
codegen/render.py | 649 --
codegen/schemas.py | 572 --
fuzz/fuzz_sse.py | 12 +-
pyproject.toml | 176 +-
src/opencode/.generated.json | 39 -
src/opencode/_aiohttp.py | 44 -
src/opencode/_logs.py | 29 -
src/opencode/_pagination.py | 206 -
src/opencode/_unions.py | 46 -
src/opencode/resources/debug.py | 314 -
src/opencode/resources/experimental.py | 939 ---
src/opencode/resources/integration.py | 1443 ----
src/opencode/resources/mcp.py | 662 --
src/opencode/resources/migration.py | 244 -
src/opencode/resources/permission.py | 938 ---
src/opencode/resources/pty.py | 674 --
src/opencode/resources/session.py | 4321 ------------
src/opencode/resources/vcs.py | 652 --
src/opencode/types/_generated.py | 8540 ------------------------
23 files changed, 127 insertions(+), 20816 deletions(-)
delete mode 100644 codegen/__init__.py
delete mode 100644 codegen/__main__.py
delete mode 100644 codegen/naming.py
delete mode 100644 codegen/operations.py
delete mode 100644 codegen/render.py
delete mode 100644 codegen/schemas.py
delete mode 100644 src/opencode/.generated.json
delete mode 100644 src/opencode/_aiohttp.py
delete mode 100644 src/opencode/_logs.py
delete mode 100644 src/opencode/_pagination.py
delete mode 100644 src/opencode/_unions.py
delete mode 100644 src/opencode/resources/debug.py
delete mode 100644 src/opencode/resources/experimental.py
delete mode 100644 src/opencode/resources/integration.py
delete mode 100644 src/opencode/resources/mcp.py
delete mode 100644 src/opencode/resources/migration.py
delete mode 100644 src/opencode/resources/permission.py
delete mode 100644 src/opencode/resources/pty.py
delete mode 100644 src/opencode/resources/session.py
delete mode 100644 src/opencode/resources/vcs.py
delete mode 100644 src/opencode/types/_generated.py
diff --git a/codegen/__init__.py b/codegen/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/codegen/__main__.py b/codegen/__main__.py
deleted file mode 100644
index 2772da1..0000000
--- a/codegen/__main__.py
+++ /dev/null
@@ -1,156 +0,0 @@
-"""Generates the OpenCode Python client from ``spec/contract.json``.
-
-Usage::
-
- uv run python -m codegen # regenerate
- uv run python -m codegen --check # fail if generated files are stale
-"""
-
-from __future__ import annotations
-
-import argparse
-import json
-import subprocess
-import sys
-import tempfile
-from pathlib import Path
-
-from .operations import build_operations, build_resources
-from .render import (
- error_classes,
- render_api_md,
- render_errors,
- render_resource_module,
- render_resources_init,
- render_types,
- render_types_init,
-)
-from .schemas import TypeRegistry, deduplicate
-
-ROOT = Path(__file__).resolve().parent.parent
-CONTRACT = ROOT / "spec" / "contract.json"
-PACKAGE = ROOT / "src" / "opencode"
-MANIFEST = PACKAGE / ".generated.json"
-
-
-def generate() -> dict[str, str]:
- """Returns ``{relative path: source}`` for every generated file."""
- contract = json.loads(CONTRACT.read_text())
- version: str = contract["opencodeVersion"]
- definitions, renames = deduplicate(contract["definitions"])
- contract["groups"] = _rewrite_refs(contract["groups"], renames)
-
- registry = TypeRegistry(definitions)
- for key in sorted(definitions):
- registry.ensure_definition(key)
- operations = build_operations(contract, registry)
- resources = build_resources(operations)
- errors = error_classes(operations, registry)
-
- files: dict[str, str] = {
- "src/opencode/types/_generated.py": render_types(registry, version),
- "src/opencode/types/__init__.py": render_types_init(registry, version),
- "src/opencode/_errors.py": render_errors(errors, version),
- "src/opencode/resources/__init__.py": render_resources_init(resources, version),
- "api.md": render_api_md(resources, version),
- }
- for name, resource in resources.items():
- files[f"src/opencode/resources/{name}.py"] = render_resource_module(resource, registry, errors, version)
-
- error_names = [entry["class"] for entry in errors.values() if not entry["is_base"]]
- init = (ROOT / "src/opencode/__init__.py").read_text()
- init = _replace_region(
- init, "error-imports", ["from ._errors import (", *[f" {name}," for name in error_names], ")"]
- )
- init = _replace_region(init, "error-exports", [f"{name!r}," for name in error_names])
- init = _replace_region(
- init,
- "api-version",
- [f"API_VERSION = {version!r}", '"""The OpenCode server version this client was generated from."""'],
- )
- files["src/opencode/__init__.py"] = init
- return files
-
-
-def _replace_region(source: str, name: str, lines: list[str]) -> str:
- """Replaces the lines between ``# `` and ``# ``, keeping their indentation."""
- start_marker, end_marker = f"# ", f"# "
- start = source.index(start_marker)
- end = source.index(end_marker)
- indent = source[source.rfind("\n", 0, start) + 1 : start]
- body = "".join(f"{indent}{line}\n" for line in lines)
- return source[: start + len(start_marker)] + "\n" + body + indent + source[end:]
-
-
-def _rewrite_refs(value: object, renames: dict[str, str]) -> object:
- if isinstance(value, dict):
- result: dict[str, object] = {}
- for key, item in value.items():
- if key == "$ref" and isinstance(item, str):
- name = item.removeprefix("#/$defs/")
- while name in renames:
- name = renames[name]
- result[key] = f"#/$defs/{name}"
- else:
- result[key] = _rewrite_refs(item, renames)
- return result
- if isinstance(value, list):
- return [_rewrite_refs(item, renames) for item in value]
- return value
-
-
-def _format(directory: Path) -> None:
- python_files = [str(path) for path in directory.rglob("*.py")]
- if not python_files:
- return
- ruff = [sys.executable, "-m", "ruff"]
- config = ["--config", str(ROOT / "pyproject.toml")]
- subprocess.run([*ruff, "check", "--fix-only", "--quiet", *config, *python_files], check=True, cwd=ROOT)
- subprocess.run([*ruff, "format", "--quiet", *config, *python_files], check=True, cwd=ROOT)
-
-
-def main() -> int:
- parser = argparse.ArgumentParser(prog="python -m codegen", description=__doc__)
- parser.add_argument("--check", action="store_true", help="exit with status 1 if generated files are out of date")
- args = parser.parse_args()
-
- files = generate()
- with tempfile.TemporaryDirectory() as tmp:
- staging = Path(tmp)
- for relative, source in files.items():
- target = staging / relative
- target.parent.mkdir(parents=True, exist_ok=True)
- target.write_text(source)
- _format(staging)
- rendered = {relative: (staging / relative).read_text() for relative in files}
-
- previous = set(json.loads(MANIFEST.read_text())) if MANIFEST.exists() else set()
- stale = sorted(previous - set(rendered))
- changed = [
- relative
- for relative, source in rendered.items()
- if not (ROOT / relative).exists() or (ROOT / relative).read_text() != source
- ]
-
- if args.check:
- if changed or stale:
- for relative in [*changed, *stale]:
- print(f"out of date: {relative}")
- print("Run `uv run python -m codegen` and commit the result.")
- return 1
- print("Generated files are up to date.")
- return 0
-
- for relative in stale:
- (ROOT / relative).unlink(missing_ok=True)
- for relative, source in rendered.items():
- target = ROOT / relative
- target.parent.mkdir(parents=True, exist_ok=True)
- target.write_text(source)
- MANIFEST.write_text(json.dumps(sorted(rendered), indent=2) + "\n")
- print(f"Generated {len(rendered)} files ({len(changed)} changed, {len(stale)} removed).")
- return 0
-
-
-if __name__ == "__main__":
- raise SystemExit(main())
diff --git a/codegen/naming.py b/codegen/naming.py
deleted file mode 100644
index ed3b99e..0000000
--- a/codegen/naming.py
+++ /dev/null
@@ -1,67 +0,0 @@
-"""Name conversion rules shared by the generator."""
-
-from __future__ import annotations
-
-import builtins
-import keyword
-import re
-
-import pydantic
-
-_WORD_SPLIT = re.compile(r"[^0-9A-Za-z]+")
-_ACRONYM_BOUNDARY = re.compile(r"([A-Z]+)([A-Z][a-z])")
-_CASE_BOUNDARY = re.compile(r"([a-z0-9])([A-Z])")
-
-PYTHON_RESERVED = frozenset(keyword.kwlist) | frozenset(keyword.softkwlist) - {"_", "type", "match", "case"}
-"""Names that cannot be used as identifiers. Soft keywords other than these are fine as names."""
-
-MODEL_RESERVED = frozenset(name for name in dir(pydantic.BaseModel) if not name.startswith("__")) | {
- "to_dict",
- "to_json",
-}
-"""Attribute names that would shadow ``pydantic.BaseModel`` or :class:`opencode.BaseModel` members."""
-
-BUILTIN_NAMES = frozenset(dir(builtins))
-
-
-def pascal(name: str) -> str:
- """``session.message.user`` -> ``SessionMessageUser``; inner capitals are kept (``persistentPty`` -> ``PersistentPty``)."""
- parts = [part for part in _WORD_SPLIT.split(name) if part]
- return "".join(part[0].upper() + part[1:] for part in parts)
-
-
-def snake(name: str) -> str:
- """``parentID`` -> ``parent_id``, ``x-opencode-ticket`` -> ``x_opencode_ticket``, ``_tag`` -> ``tag``."""
- value = _WORD_SPLIT.sub("_", name)
- value = _ACRONYM_BOUNDARY.sub(r"\1_\2", value)
- value = _CASE_BOUNDARY.sub(r"\1_\2", value)
- value = re.sub(r"_+", "_", value).strip("_").lower()
- if not value:
- raise ValueError(f"Cannot derive a Python name from {name!r}")
- if value[0].isdigit():
- value = f"n{value}"
- return value
-
-
-def safe_identifier(name: str, *, reserved: frozenset[str] = frozenset()) -> str:
- """Appends ``_`` until the name is not a keyword or one of ``reserved``."""
- while name in PYTHON_RESERVED or name in reserved:
- name = f"{name}_"
- return name
-
-
-def docstring(text: str | None, indent: str) -> str | None:
- """Formats text as a triple-quoted docstring body, or returns ``None`` when there is nothing to say."""
- if text is None:
- return None
- text = text.strip()
- if not text:
- return None
- text = text.replace("\\", "\\\\").replace('"""', '\\"\\"\\"')
- if text.endswith('"'):
- text += " "
- lines = text.splitlines()
- if len(lines) == 1:
- return f'{indent}"""{lines[0]}"""'
- body = "\n".join(f"{indent}{line}".rstrip() for line in lines[1:])
- return f'{indent}"""{lines[0]}\n{body}\n{indent}"""'
diff --git a/codegen/operations.py b/codegen/operations.py
deleted file mode 100644
index 9c30191..0000000
--- a/codegen/operations.py
+++ /dev/null
@@ -1,220 +0,0 @@
-"""Builds the resource tree and method signatures from contract endpoints."""
-
-from __future__ import annotations
-
-import re
-from dataclasses import dataclass, field
-from typing import Any
-
-from .naming import pascal, safe_identifier, snake
-from .schemas import GenerationError, Schema, TypeRegistry
-
-RESERVED_PARAMS = frozenset({"self", "extra_headers", "extra_query", "extra_body", "timeout"})
-RESOURCE_RESERVED = frozenset({"with_options", "close", "request", "stream", "page"})
-
-
-@dataclass
-class Param:
- name: str
- wire: str
- source: str
- type: str
- required: bool
- nullable: bool
- doc: str | None
-
-
-@dataclass
-class ErrorInfo:
- status: int
- tag: str | None
- schema: Schema
-
-
-@dataclass
-class Operation:
- group: str
- client_path: list[str]
- method_name: str
- http_method: str
- path: str
- operation_id: str
- summary: str | None
- description: str | None
- deprecated: bool
- experimental: bool
- params: list[Param]
- payload_mode: str | None
- kind: str
- """``json``, ``void``, ``binary``, ``stream`` or ``page``."""
- return_type: str
- response_type: str | None
- unwrap: bool
- errors: list[ErrorInfo]
-
- @property
- def path_params(self) -> list[Param]:
- return [param for param in self.params if param.source in ("params", "wildcard")]
-
- @property
- def keyword_params(self) -> list[Param]:
- return [param for param in self.params if param.source not in ("params", "wildcard")]
-
-
-@dataclass
-class Resource:
- name: str
- """Attribute name on the parent (``session``, ``revert``)."""
- class_name: str
- path: list[str]
- operations: list[Operation] = field(default_factory=list)
- children: dict[str, Resource] = field(default_factory=dict)
-
-
-def _is_page(registry: TypeRegistry, schema: Schema, params: list[Param]) -> Schema | None:
- """Returns the item schema when a response is a cursor page: ``{data: [...], cursor: {next, previous}}``."""
- resolved = registry.resolve(schema)
- properties = resolved.get("properties", {})
- if set(properties) != {"data", "cursor"} or not any(param.wire == "cursor" for param in params):
- return None
- cursor = registry.resolve(properties["cursor"])
- if set(cursor.get("properties", {})) != {"next", "previous"}:
- return None
- data = registry.resolve(properties["data"])
- if data.get("type") != "array":
- return None
- return data.get("items", {})
-
-
-def build_operations(contract: dict[str, Any], registry: TypeRegistry) -> list[Operation]:
- operations: list[Operation] = []
- for group in contract["groups"]:
- for endpoint in group["endpoints"]:
- operations.append(_operation(group["name"], endpoint, registry))
- return operations
-
-
-def _operation(group: str, endpoint: dict[str, Any], registry: TypeRegistry) -> Operation:
- client_path: list[str] = endpoint["clientPath"]
- context = pascal(group) + "".join(pascal(part) for part in client_path)
- method_name = safe_identifier(snake(client_path[-1]), reserved=RESOURCE_RESERVED)
-
- params: list[Param] = []
- used: set[str] = set()
- for item in endpoint["input"]:
- name = safe_identifier(snake(item["name"]), reserved=RESERVED_PARAMS)
- if name in used:
- raise GenerationError(f"{endpoint['operationId']}: parameter {item['name']!r} collides as {name!r}")
- used.add(name)
- ref = registry.type_of(
- item["schema"],
- context=pascal(item["name"]) if item["source"] == "query" else f"{context}{pascal(item['name'])}",
- )
- params.append(
- Param(
- name=name,
- wire=item["name"],
- source=item["source"],
- type=ref.expr,
- required=not item["optional"],
- nullable=ref.nullable,
- doc=item["schema"].get("description") or registry._ref_description(item["schema"]),
- )
- )
-
- success = endpoint["success"]
- kind: str = success["kind"]
- unwrap = bool(success.get("unwrapData"))
- response_type: str | None = None
- if kind == "void":
- return_type = "None"
- elif kind == "binary":
- return_type = "bytes"
- elif kind == "stream":
- item = registry.type_of(success["schema"], context=f"{context}Event")
- return_type = item.expr if not item.nullable else f"{item.expr} | None"
- response_type = return_type
- else:
- schema: Schema = success["schema"]
- page_item = None if unwrap else _is_page(registry, schema, params)
- if page_item is not None:
- kind = "page"
- item = registry.type_of(page_item, context=f"{context}Item")
- response = registry.type_of(schema, context=f"{context}Response")
- return_type = item.expr
- response_type = response.expr
- else:
- target = schema
- target_context = f"{context}Response"
- if unwrap:
- if "$ref" in schema:
- # Match the name the data field already got when its definition was declared.
- target_context = registry.def_names[schema["$ref"].removeprefix("#/$defs/")] + "Data"
- target = registry.resolve(schema)["properties"]["data"]
- ref = registry.type_of(target, context=target_context)
- return_type = ref.expr if not ref.nullable else f"{ref.expr} | None"
- response_type = return_type
- if kind == "value":
- kind = "json"
-
- path: str = endpoint["path"]
- return Operation(
- group=group,
- client_path=client_path,
- method_name=method_name,
- http_method=endpoint["method"],
- path=path,
- operation_id=endpoint["operationId"],
- summary=endpoint.get("summary"),
- description=endpoint.get("description"),
- deprecated=bool(endpoint.get("deprecated")),
- experimental="/experimental/" in path or endpoint["operationId"].startswith("experimental."),
- params=params,
- payload_mode=(endpoint.get("payload") or {}).get("mode"),
- kind=kind,
- return_type=return_type,
- response_type=response_type,
- unwrap=unwrap,
- errors=[
- ErrorInfo(status=error["status"], tag=error.get("tag"), schema=error["schema"])
- for error in endpoint["errors"]
- ],
- )
-
-
-def build_resources(operations: list[Operation]) -> dict[str, Resource]:
- roots: dict[str, Resource] = {}
- for operation in operations:
- group = safe_identifier(snake(operation.group), reserved=RESOURCE_RESERVED)
- root = roots.setdefault(group, Resource(name=group, class_name=pascal(operation.group), path=[operation.group]))
- node = root
- for part in operation.client_path[:-1]:
- child_name = safe_identifier(snake(part), reserved=RESOURCE_RESERVED)
- if child_name not in node.children:
- node.children[child_name] = Resource(
- name=child_name, class_name=node.class_name + pascal(part), path=[*node.path, part]
- )
- node = node.children[child_name]
- if any(existing.method_name == operation.method_name for existing in node.operations):
- raise GenerationError(f"Duplicate method {operation.method_name} on {node.class_name}")
- if operation.method_name in node.children:
- raise GenerationError(f"Method {operation.method_name} collides with a sub-resource on {node.class_name}")
- node.operations.append(operation)
- return roots
-
-
-def python_path(operation: Operation) -> str:
- """Builds the f-string body for an endpoint path such as ``/api/session/:sessionID``."""
- by_wire = {param.wire: param for param in operation.params if param.source == "params"}
-
- def replace(match: re.Match[str]) -> str:
- wire = match.group(1)
- if wire not in by_wire:
- raise GenerationError(f"{operation.operation_id}: missing path parameter {wire}")
- return "{encode_path_segment(" + by_wire[wire].name + ")}"
-
- path = re.sub(r":([A-Za-z_][A-Za-z0-9_]*)", replace, operation.path)
- if path.endswith("*"):
- wildcard = next(param for param in operation.params if param.source == "wildcard")
- path = path[:-1] + "{encode_path(" + wildcard.name + ")}"
- return path
diff --git a/codegen/render.py b/codegen/render.py
deleted file mode 100644
index 3a9588b..0000000
--- a/codegen/render.py
+++ /dev/null
@@ -1,649 +0,0 @@
-"""Renders generated Python source."""
-
-from __future__ import annotations
-
-import re
-from typing import Any
-
-from .naming import BUILTIN_NAMES, docstring, safe_identifier, snake
-from .operations import Operation, Param, Resource, python_path
-from .schemas import AliasDecl, ClassDecl, GenerationError, TypeRegistry
-
-HEADER = (
- "# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.\n"
- "# OpenCode {version}\n"
-)
-
-_IDENTIFIER = re.compile(r"\b[A-Za-z_][A-Za-z0-9_]*\b")
-
-STATUS_BASES = {
- 400: "BadRequestError",
- 401: "AuthenticationError",
- 403: "PermissionDeniedError",
- 404: "NotFoundError",
- 409: "ConflictError",
- 413: "ContentTooLargeError",
- 422: "UnprocessableEntityError",
- 429: "RateLimitError",
- 500: "InternalServerError",
- 501: "NotImplementedServerError",
- 503: "ServiceUnavailableError",
-}
-
-
-def _names_in(expr: str) -> set[str]:
- return set(_IDENTIFIER.findall(expr))
-
-
-# -- types --------------------------------------------------------------------------------------------------
-
-
-def _field_line(field_decl: Any) -> str:
- annotation = field_decl.type
- if (field_decl.nullable or not field_decl.required) and annotation != "Any":
- annotation = f"{annotation} | None"
- args: list[str] = []
- if field_decl.wire != field_decl.name:
- args.append(f"validation_alias={field_decl.wire!r}")
- args.append(f"serialization_alias={field_decl.wire!r}")
- if field_decl.literal_default is not None and field_decl.required:
- default: str | None = field_decl.literal_default
- elif not field_decl.required:
- default = "None"
- if not field_decl.nullable:
- args.append("exclude_if=is_none")
- else:
- default = None
- if args:
- inner = ", ".join(([f"default={default}"] if default is not None else []) + args)
- return f" {field_decl.name}: {annotation} = Field({inner})"
- if default is not None:
- return f" {field_decl.name}: {annotation} = {default}"
- return f" {field_decl.name}: {annotation}"
-
-
-def render_class(decl: ClassDecl) -> str:
- lines = [f"class {decl.name}(BaseModel):"]
- doc = docstring(decl.doc, " ")
- if doc:
- lines.append(doc)
- lines.append("")
- always_set = [field.name for field in decl.fields if field.literal_default is not None and field.required]
- if always_set:
- lines.append(f" __opencode_always_set__: ClassVar[tuple[str, ...]] = {tuple(always_set)!r}")
- lines.append("")
- if not decl.fields and not doc:
- lines.append(" pass")
- for field_decl in decl.fields:
- lines.append(_field_line(field_decl))
- field_doc = docstring(field_decl.doc, " ")
- if field_doc:
- lines.append(field_doc)
- lines.append("")
- return "\n".join(lines).rstrip() + "\n"
-
-
-def render_alias(decl: AliasDecl) -> str:
- lines = [f"{decl.name}: TypeAlias = {decl.expr}"]
- doc = docstring(decl.doc, "")
- if doc:
- lines.append(doc)
- return "\n".join(lines) + "\n"
-
-
-def _alias_order(aliases: dict[str, AliasDecl]) -> list[AliasDecl]:
- names = set(aliases)
- deps = {name: (_names_in(decl.expr) & names) - {name} for name, decl in aliases.items()}
- ordered: list[AliasDecl] = []
- state: dict[str, int] = {}
-
- def visit(name: str, chain: list[str]) -> None:
- if state.get(name) == 2:
- return
- if state.get(name) == 1:
- raise GenerationError(f"Alias cycle: {' -> '.join([*chain, name])}")
- state[name] = 1
- for dep in sorted(deps[name]):
- visit(dep, [*chain, name])
- state[name] = 2
- ordered.append(aliases[name])
-
- for name in sorted(aliases):
- visit(name, [])
- return ordered
-
-
-def render_types(registry: TypeRegistry, version: str) -> str:
- out = [
- HEADER.format(version=version),
- '"""Models for every OpenCode API type.\n\n'
- "Each class mirrors a schema of the OpenCode server. Attributes are snake_case; the wire names are kept\n"
- 'as aliases, so models validate server JSON and serialize back to it unchanged.\n"""\n',
- "from __future__ import annotations\n",
- "from typing import Annotated, Any, ClassVar, Literal, TypeAlias\n",
- "from pydantic import Discriminator, Field, Tag\n",
- "from .._models import BaseModel, is_none",
- "from .._types import OpenEnum",
- "from .._unions import discriminator\n\n",
- ]
- for name in sorted(registry.classes):
- out.append(render_class(registry.classes[name]))
- out.append("\n")
- for alias in _alias_order(registry.aliases):
- out.append(render_alias(alias))
- out.append("\n")
- exported = sorted([*registry.classes, *registry.aliases])
- out.append("__all__ = [\n" + "".join(f" {name!r},\n" for name in exported) + "]\n")
- return "\n".join(out)
-
-
-def render_types_init(registry: TypeRegistry, version: str) -> str:
- exported = sorted([*registry.classes, *registry.aliases])
- lines = [
- HEADER.format(version=version),
- '"""Typed models for OpenCode API requests, responses and events."""\n',
- "from __future__ import annotations\n",
- "from ._generated import (",
- *[f" {name} as {name}," for name in exported],
- ")\n",
- "__all__ = [",
- *[f" {name!r}," for name in exported],
- "]\n",
- ]
- return "\n".join(lines)
-
-
-# -- errors -------------------------------------------------------------------------------------------------
-
-
-def error_classes(operations: list[Operation], registry: TypeRegistry) -> dict[str, dict[str, Any]]:
- """Collects one entry per server error tag: class name, base class and body model."""
- tags: dict[str, dict[str, Any]] = {}
- for operation in operations:
- for error in operation.errors:
- if error.tag is None:
- continue
- body = registry.type_of(error.schema, context=f"{error.tag}Body").expr
- entry = tags.setdefault(error.tag, {"statuses": set(), "body": body})
- entry["statuses"].add(error.status)
- if entry["body"] != body:
- raise GenerationError(f"Error tag {error.tag} has different bodies: {entry['body']} / {body}")
- result: dict[str, dict[str, Any]] = {}
- for tag, entry in sorted(tags.items()):
- statuses = sorted(entry["statuses"])
- if len(statuses) != 1:
- raise GenerationError(f"Error tag {tag} is declared with several statuses: {statuses}")
- status = statuses[0]
- base = STATUS_BASES.get(status) or ("InternalServerError" if status >= 500 else "APIStatusError")
- class_name = tag if tag not in BUILTIN_NAMES else f"Remote{tag}"
- result[tag] = {
- "class": class_name,
- "base": base,
- "status": status,
- "body": entry["body"],
- "is_base": tag == base,
- }
- return result
-
-
-def render_errors(errors: dict[str, dict[str, Any]], version: str) -> str:
- bases = sorted(
- {entry["base"] for entry in errors.values()} | {entry["class"] for entry in errors.values() if entry["is_base"]}
- )
- bodies = sorted({entry["body"] for entry in errors.values()})
- lines = [
- HEADER.format(version=version),
- '"""Errors the OpenCode server declares, keyed by their ``_tag``."""\n',
- "from __future__ import annotations\n",
- "from typing import TYPE_CHECKING\n",
- "from ._exceptions import (",
- " APIStatusError,",
- *[f" {name}," for name in bases if name != "APIStatusError"],
- ")\n",
- "if TYPE_CHECKING:",
- " from .types import (",
- *[f" {name}," for name in bodies],
- " )\n",
- "__all__ = [",
- ' "TAGGED_ERRORS",',
- *[f" {entry['class']!r}," for entry in errors.values() if not entry["is_base"]],
- "]\n\n",
- ]
- for tag, entry in errors.items():
- if entry["is_base"]:
- continue
- lines.append(f"class {entry['class']}({entry['base']}):")
- lines.append(f' """The server answered {entry["status"]} with ``_tag`` ``{tag}``."""\n')
- lines.append(f" tag = {tag!r}\n")
- lines.append(" @property")
- lines.append(f' def error(self) -> "{entry["body"]}":')
- lines.append(' """The error body as a typed model."""')
- lines.append(f" from .types import {entry['body']}\n")
- lines.append(f" return self._typed_body({entry['body']})\n\n")
- lines.append("TAGGED_ERRORS: dict[str, type[APIStatusError]] = {")
- for tag, entry in errors.items():
- lines.append(f" {tag!r}: {entry['class']},")
- lines.append("}\n")
- return "\n".join(lines)
-
-
-# -- resources ----------------------------------------------------------------------------------------------
-
-
-def _resource_type(expr: str) -> str:
- """Qualifies ``list`` / ``dict`` so a method named ``list`` cannot shadow them in annotations."""
- return re.sub(r"(? str:
- expr = param.type
- if expr.startswith("list["):
- expr = "Sequence[" + expr[len("list[") :]
- elif expr.startswith("dict[str, "):
- expr = "Mapping[str, " + expr[len("dict[str, ") :]
- expr = _resource_type(expr)
- if param.nullable:
- expr = f"{expr} | None"
- if not param.required:
- expr = f"{expr} | NotGiven"
- return expr
-
-
-def _method_doc(operation: Operation, errors: dict[str, dict[str, Any]], indent: str) -> str:
- parts: list[str] = []
- if operation.summary:
- parts.append(operation.summary.rstrip(".") + ".")
- if operation.description:
- parts.append(operation.description)
- notes: list[str] = []
- if operation.experimental:
- notes.append("This endpoint is experimental in OpenCode and may change without notice.")
- if operation.deprecated:
- notes.append("Deprecated.")
- if notes:
- parts.append(" ".join(notes))
- documented = [param for param in operation.params if param.doc]
- if documented:
- parts.append("Args:\n" + "\n".join(f" {param.name}: {_one_line(param.doc or '')}" for param in documented))
- raised: dict[str, int] = {}
- for error in operation.errors:
- if error.tag and error.tag in errors:
- raised.setdefault(errors[error.tag]["class"], errors[error.tag]["status"])
- if raised:
- parts.append(
- "Raises:\n" + "\n".join(f" {name}: The server answered {status}." for name, status in raised.items())
- )
- parts.append(f"``{operation.http_method} {operation.path}``")
- return docstring("\n\n".join(parts), indent) or ""
-
-
-def _one_line(text: str) -> str:
- return " ".join(text.split())
-
-
-def _render_method(operation: Operation, errors: dict[str, dict[str, Any]], *, is_async: bool) -> list[str]:
- args = [" self,"]
- for param in operation.path_params:
- args.append(f" {param.name}: {_input_type(param)},")
- args.append(" *,")
- required = [param for param in operation.keyword_params if param.required]
- optional = [param for param in operation.keyword_params if not param.required]
- for param in [*required, *optional]:
- default = "" if param.required else " = NOT_GIVEN"
- args.append(f" {param.name}: {_input_type(param)}{default},")
- args += [
- " extra_headers: Headers | None = None,",
- " extra_query: Query | None = None,",
- " extra_body: Body | None = None,",
- " timeout: Timeout | NotGiven = NOT_GIVEN,",
- ]
-
- return_type = _resource_type(operation.return_type)
- if operation.kind == "page":
- returns = f"{'AsyncPaginator' if is_async else 'SyncCursorPage'}[{return_type}]"
- elif operation.kind == "stream":
- returns = f"{'AsyncStream' if is_async else 'Stream'}[{return_type}]"
- else:
- returns = return_type
-
- define = "async def" if is_async and operation.kind != "page" else "def"
- lines = [f" {define} {operation.method_name}(", *args, f" ) -> {returns}:"]
- doc = _method_doc(operation, errors, " ")
- if doc:
- lines.append(doc)
-
- options = [f' method="{operation.http_method}",', f' path=f"{python_path(operation)}",']
- query = [param for param in operation.params if param.source == "query"]
- headers = [param for param in operation.params if param.source == "headers"]
- payload = [param for param in operation.params if param.source == "payload"]
- if query:
- entries = []
- for param in query:
- value = f"self._location({param.name})" if param.wire == "location" else param.name
- entries.append(f"{param.wire!r}: {value}")
- options.append(" query={" + ", ".join(entries) + "},")
- if headers:
- options.append(" headers={" + ", ".join(f"{param.wire!r}: {param.name}" for param in headers) + "},")
- if payload:
- if operation.payload_mode == "binary":
- options.append(f" content=bytes({payload[0].name}),")
- elif operation.payload_mode == "opaque":
- options.append(f" body={payload[0].name},")
- else:
- entries = [
- f"{param.wire!r}: " + (f"self._location({param.name})" if param.wire == "location" else param.name)
- for param in payload
- ]
- options.append(" body={" + ", ".join(entries) + "},")
- options += [
- " extra_headers=extra_headers,",
- " extra_query=extra_query,",
- " extra_body=extra_body,",
- " timeout=timeout,",
- ]
- request_options = [" RequestOptions(", *[f" {line}" for line in options], " ),"]
-
- awaiting = "await " if is_async else ""
- if operation.kind == "page":
- lines += [
- " return self._client.page(",
- *request_options,
- f" response_type={_resource_type(operation.response_type or 'Any')!r},",
- " )",
- ]
- elif operation.kind == "stream":
- lines += [
- f" return {awaiting}self._client.stream(",
- *request_options,
- f" item_type={_resource_type(operation.response_type or 'Any')!r},",
- " )",
- ]
- elif operation.kind == "void":
- # Returned so ``with_raw_response`` can hand back the response; a plain call returns ``None``.
- lines += [
- f' return cast("None", {awaiting}self._client.request(',
- *request_options,
- ' kind="void",',
- " ))",
- ]
- elif operation.kind == "binary":
- lines += [
- f' return cast("bytes", {awaiting}self._client.request(',
- *request_options,
- ' kind="binary",',
- " ))",
- ]
- else:
- lines += [
- f" return cast({returns!r}, {awaiting}self._client.request(",
- *request_options,
- f" response_type={_resource_type(operation.response_type or 'Any')!r},",
- *([" unwrap=True,"] if operation.unwrap else []),
- " ))",
- ]
- return lines
-
-
-# Hand-written methods mixed into generated resources: resource path -> (module, sync mixin, async mixin).
-# The mixins subclass the resource base classes and cover endpoints the generator cannot express (WebSockets).
-MIXINS: dict[tuple[str, ...], tuple[str, str, str]] = {
- ("pty",): ("_pty", "PtyAttachMixin", "AsyncPtyAttachMixin"),
- ("session",): ("lib._session", "SessionHelpers", "AsyncSessionHelpers"),
- ("experimental", "persistentPty"): ("_pty", "PersistentPtyAttachMixin", "AsyncPersistentPtyAttachMixin"),
-}
-
-
-def _mixins(resource: Resource) -> list[tuple[str, str, str]]:
- found = [MIXINS[tuple(resource.path)]] if tuple(resource.path) in MIXINS else []
- for child in resource.children.values():
- found += _mixins(child)
- return found
-
-
-def _resource_classes(resource: Resource, errors: dict[str, dict[str, Any]], *, is_async: bool) -> list[str]:
- blocks: list[str] = []
- for child in resource.children.values():
- blocks += _resource_classes(child, errors, is_async=is_async)
- prefix = "Async" if is_async else ""
- base = "AsyncAPIResource" if is_async else "SyncAPIResource"
- if tuple(resource.path) in MIXINS:
- base = MIXINS[tuple(resource.path)][2 if is_async else 1]
- lines = [f"class {prefix}{resource.class_name}Resource({base}):"]
- title = ".".join(resource.path)
- lines.append(f' """Operations under ``client.{_attr_path(resource)}`` (OpenCode ``{title}``)."""\n')
- for name, child in resource.children.items():
- lines += [
- " @cached_property",
- f" def {name}(self) -> {prefix}{child.class_name}Resource:",
- f' """Operations under ``client.{_attr_path(child)}``."""',
- f" return {prefix}{child.class_name}Resource(self._client)\n",
- ]
- for kind, summary in _WRAPPERS:
- lines += [
- " @cached_property",
- f" def {kind}(self) -> {prefix}{resource.class_name}Resource{_WRAPPER_SUFFIX[kind]}:",
- f' """{summary}"""',
- f" return {prefix}{resource.class_name}Resource{_WRAPPER_SUFFIX[kind]}(self)\n",
- ]
- for operation in resource.operations:
- lines += _render_method(operation, errors, is_async=is_async)
- lines.append("")
- blocks.append("\n".join(lines).rstrip() + "\n")
- for kind, _ in _WRAPPERS:
- blocks.append(_wrapper_class(resource, kind, is_async=is_async))
- return blocks
-
-
-_WRAPPERS = (
- (
- "with_raw_response",
- "Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value.",
- ),
- (
- "with_streaming_response",
- "Same methods, returning a context manager over the HTTP response without reading the body.",
- ),
-)
-_WRAPPER_SUFFIX = {"with_raw_response": "WithRawResponse", "with_streaming_response": "WithStreamingResponse"}
-_WRAPPER_FUNCTION = {
- ("with_raw_response", False): "to_raw_response_wrapper",
- ("with_streaming_response", False): "to_streamed_response_wrapper",
- ("with_raw_response", True): "async_to_raw_response_wrapper",
- ("with_streaming_response", True): "async_to_streamed_response_wrapper",
-}
-
-
-def _wrapper_class(resource: Resource, kind: str, *, is_async: bool) -> str:
- prefix = "Async" if is_async else ""
- suffix = _WRAPPER_SUFFIX[kind]
- name = f"{prefix}{resource.class_name}Resource"
- wrap = _WRAPPER_FUNCTION[(kind, is_async)]
- lines = [
- f"class {name}{suffix}:",
- f' """``client.{_attr_path(resource)}.{kind}``."""\n',
- f" def __init__(self, resource: {name}) -> None:",
- " self._resource = resource",
- *[
- f" self.{operation.method_name} = {wrap}(resource.{operation.method_name})"
- for operation in resource.operations
- ],
- "",
- ]
- for child_name, child in resource.children.items():
- lines += [
- " @cached_property",
- f" def {child_name}(self) -> {prefix}{child.class_name}Resource{suffix}:",
- f' """``client.{_attr_path(child)}.{kind}``."""',
- f" return {prefix}{child.class_name}Resource{suffix}(self._resource.{child_name})\n",
- ]
- return "\n".join(lines).rstrip() + "\n"
-
-
-def _attr_path(resource: Resource) -> str:
- return ".".join(safe_identifier(snake(part)) for part in resource.path)
-
-
-def _collect_type_names(resource: Resource, known: set[str]) -> set[str]:
- names: set[str] = set()
- for operation in resource.operations:
- for param in operation.params:
- names |= _names_in(param.type) & known
- names |= _names_in(operation.return_type) & known
- if operation.response_type:
- names |= _names_in(operation.response_type) & known
- for child in resource.children.values():
- names |= _collect_type_names(child, known)
- return names
-
-
-def _all_operations(resource: Resource) -> list[Operation]:
- items = list(resource.operations)
- for child in resource.children.values():
- items += _all_operations(child)
- return items
-
-
-def render_resource_module(
- resource: Resource, registry: TypeRegistry, errors: dict[str, dict[str, Any]], version: str
-) -> str:
- known = set(registry.classes) | set(registry.aliases)
- type_names = sorted(_collect_type_names(resource, known))
- operations = _all_operations(resource)
- source = "\n".join(
- [*_resource_classes(resource, errors, is_async=False), *_resource_classes(resource, errors, is_async=True)]
- )
- uses = _names_in(source)
- typing_names = sorted(({"Any", "Literal", "cast"} & uses) | ({"TYPE_CHECKING"} if type_names else set[str]()))
- abc_names = sorted({"Mapping", "Sequence"} & uses)
- runtime: list[str] = []
- if "encode_path_segment" in uses:
- runtime.append("encode_path_segment")
- if "encode_path" in uses:
- runtime.append("encode_path")
- lines = [
- HEADER.format(version=version),
- f'"""The ``{resource.name}`` resource."""\n',
- "from __future__ import annotations\n",
- "import builtins" if "builtins" in uses else "",
- *([f"from collections.abc import {', '.join(abc_names)}"] if abc_names else []),
- "from functools import cached_property" if "cached_property" in uses else "",
- *([f"from typing import {', '.join(typing_names)}"] if typing_names else []),
- "",
- "from .._base_client import RequestOptions",
- *(
- ["from .._pagination import AsyncPaginator, SyncCursorPage"]
- if any(op.kind == "page" for op in operations)
- else []
- ),
- *[f"from ..{module} import {', '.join(sorted({sync, async_}))}" for module, sync, async_ in _mixins(resource)],
- *([f"from .._qs import {', '.join(runtime)}"] if runtime else []),
- "from .._resource import AsyncAPIResource, SyncAPIResource",
- "from .._response import (",
- " async_to_raw_response_wrapper,",
- " async_to_streamed_response_wrapper,",
- " to_raw_response_wrapper,",
- " to_streamed_response_wrapper,",
- ")",
- *(["from .._streaming import AsyncStream, Stream"] if any(op.kind == "stream" for op in operations) else []),
- "from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout"
- + (", OpenEnum" if "OpenEnum" in uses else ""),
- # Models load on first use (see ``type_adapter``), so importing the client does not build them all.
- *(
- [
- "",
- "if TYPE_CHECKING:",
- " from ..types import (",
- *[f" {name}," for name in type_names],
- " )",
- ]
- if type_names
- else []
- ),
- "",
- f"__all__ = [{', '.join(repr(prefix + resource.class_name + 'Resource') for prefix in ('', 'Async'))}]\n\n",
- source,
- ]
- return "\n".join(line for line in lines if line is not None)
-
-
-def render_resources_init(resources: dict[str, Resource], version: str) -> str:
- imports = []
- sync_props = []
- async_props = []
- for name, resource in sorted(resources.items()):
- module = name
- imports.append(f"from .{module} import Async{resource.class_name}Resource, {resource.class_name}Resource")
- sync_props += [
- " @cached_property",
- f" def {name}(self) -> {resource.class_name}Resource:",
- f' """Operations for OpenCode ``{resource.path[0]}``."""',
- f' return {resource.class_name}Resource(cast("Opencode", self))\n',
- ]
- async_props += [
- " @cached_property",
- f" def {name}(self) -> Async{resource.class_name}Resource:",
- f' """Operations for OpenCode ``{resource.path[0]}``."""',
- f' return Async{resource.class_name}Resource(cast("AsyncOpencode", self))\n',
- ]
- lines = [
- HEADER.format(version=version),
- '"""Resource groups attached to the clients."""\n',
- "from __future__ import annotations\n",
- "from functools import cached_property",
- "from typing import TYPE_CHECKING, cast\n",
- *imports,
- "",
- "if TYPE_CHECKING:",
- " from .._client import AsyncOpencode, Opencode\n",
- '__all__ = ["AsyncResources", "SyncResources"]\n\n',
- "class SyncResources:",
- ' """Resource groups of :class:`opencode.Opencode`."""\n',
- *sync_props,
- "",
- "class AsyncResources:",
- ' """Resource groups of :class:`opencode.AsyncOpencode`."""\n',
- *async_props,
- ]
- return "\n".join(lines).rstrip() + "\n"
-
-
-# -- api.md -------------------------------------------------------------------------------------------------
-
-
-def render_api_md(resources: dict[str, Resource], version: str) -> str:
- lines = [
- "# API reference",
- "",
- f"Generated from the OpenCode {version} contract. Every method exists on both `Opencode` and `AsyncOpencode`;",
- "async methods return awaitables, async list methods return `AsyncPaginator`, and async streams return",
- "`AsyncStream`. Types live in `opencode.types`.",
- "",
- ]
-
- def walk(resource: Resource, attr: str) -> None:
- if resource.operations:
- lines.append(f"## `client.{attr}`")
- lines.append("")
- lines.append("| Method | HTTP | Returns | Summary |")
- lines.append("| --- | --- | --- | --- |")
- for operation in resource.operations:
- returns = operation.return_type
- if operation.kind == "page":
- returns = f"SyncCursorPage[{returns}]"
- elif operation.kind == "stream":
- returns = f"Stream[{returns}]"
- summary = (operation.summary or "").replace("|", "\\|")
- if operation.experimental:
- summary += " *(experimental)*"
- signature = ", ".join(param.name for param in operation.path_params)
- lines.append(
- f"| `{operation.method_name}({signature})` | `{operation.http_method} {operation.path}` | "
- f"`{returns.replace('|', '|')}` | {summary} |"
- )
- lines.append("")
- for name, child in resource.children.items():
- walk(child, f"{attr}.{name}")
-
- for name, resource in sorted(resources.items()):
- walk(resource, name)
- return "\n".join(lines)
diff --git a/codegen/schemas.py b/codegen/schemas.py
deleted file mode 100644
index ffb4082..0000000
--- a/codegen/schemas.py
+++ /dev/null
@@ -1,572 +0,0 @@
-"""Translates the contract's JSON Schemas into Python type declarations."""
-
-from __future__ import annotations
-
-import json
-import re
-from dataclasses import dataclass, field
-from typing import Any
-
-from .naming import BUILTIN_NAMES, MODEL_RESERVED, pascal, safe_identifier, snake
-
-Schema = dict[str, Any]
-
-UNKNOWN_TAG = "__unknown__"
-
-# Names the generated types module imports. Generated declarations must not shadow them.
-MODULE_RESERVED = frozenset(
- {
- "Annotated",
- "Any",
- "BaseModel",
- "ClassVar",
- "Discriminator",
- "Field",
- "Literal",
- "OpenEnum",
- "Tag",
- "TypeAlias",
- "discriminator",
- "is_none",
- }
-)
-
-
-@dataclass
-class FieldDecl:
- name: str
- wire: str
- type: str
- required: bool
- nullable: bool
- doc: str | None
- literal_default: str | None = None
- """For a single-value literal (a union tag), the value to default to."""
-
-
-@dataclass
-class ClassDecl:
- name: str
- doc: str | None
- fields: list[FieldDecl]
- source: str
- unknown_for: str | None = None
- """Set on fallback classes generated for a union."""
-
-
-@dataclass
-class AliasDecl:
- name: str
- expr: str
- doc: str | None
- source: str
- deps: set[str] = field(default_factory=set)
- """Names of other aliases this one evaluates at import time."""
-
-
-@dataclass
-class TypeRef:
- expr: str
- nullable: bool = False
- aliases: set[str] = field(default_factory=set)
- """Aliases referenced by the expression; aliases are evaluated eagerly, so their order matters."""
-
-
-class GenerationError(Exception):
- pass
-
-
-def _canonical(value: Any) -> str:
- return json.dumps(value, sort_keys=True, separators=(",", ":"))
-
-
-def _without_docs(value: Any) -> Any:
- if isinstance(value, dict):
- return {k: _without_docs(v) for k, v in value.items() if k not in ("description", "title", "examples")}
- if isinstance(value, list):
- return [_without_docs(v) for v in value]
- return value
-
-
-def deduplicate(definitions: dict[str, Schema]) -> tuple[dict[str, Schema], dict[str, str]]:
- """Merges ``Name_N`` definitions into ``Name`` when they have the same shape.
-
- Effect numbers repeated identifiers per schema instance. Equal shapes collapse to one name; shapes that really
- differ keep their suffix.
- """
- renames: dict[str, str] = {}
- pattern = re.compile(r"^(.*)_(\d+)$")
-
- def rewrite(value: Any) -> Any:
- if isinstance(value, dict):
- result = {}
- for key, item in value.items():
- if key == "$ref" and isinstance(item, str):
- name = item.removeprefix("#/$defs/")
- while name in renames:
- name = renames[name]
- result[key] = f"#/$defs/{name}"
- else:
- result[key] = rewrite(item)
- return result
- if isinstance(value, list):
- return [rewrite(item) for item in value]
- return value
-
- current = dict(definitions)
- while True:
- changed = False
- for name in sorted(current):
- match = pattern.match(name)
- if not match or match.group(1) not in current or name in renames:
- continue
- base = match.group(1)
- if _canonical(_without_docs(rewrite(current[name]))) == _canonical(_without_docs(rewrite(current[base]))):
- renames[name] = base
- changed = True
- if not changed:
- break
- merged = {name: rewrite(schema) for name, schema in current.items() if name not in renames}
- return merged, renames
-
-
-def definition_python_name(key: str) -> str:
- """Maps a contract definition name to a Python class or alias name."""
- suffix = ""
- match = re.match(r"^(.*)_(\d+)$", key)
- if match:
- key, suffix = match.group(1), match.group(2)
- if key == "V2Event":
- name = "Event"
- elif key.startswith("V2Event."):
- name = pascal(key.removeprefix("V2Event.")) + "Event"
- elif key[:1].islower():
- name = pascal(key) + "Event"
- else:
- name = key
- if name.endswith("Encoded"):
- name = name.removesuffix("Encoded")
- if name.endswith("Error"):
- name += "Body"
- name = pascal(name)
- return name + suffix
-
-
-class TypeRegistry:
- """Builds declarations on demand while walking schemas."""
-
- def __init__(self, definitions: dict[str, Schema]) -> None:
- self.definitions = definitions
- self.def_names: dict[str, str] = {}
- self.classes: dict[str, ClassDecl] = {}
- self.aliases: dict[str, AliasDecl] = {}
- self._inline_by_shape: dict[str, str] = {}
- self._taken: set[str] = set(MODULE_RESERVED)
- for key in sorted(definitions):
- name = definition_python_name(key)
- if name in self._taken:
- raise GenerationError(f"Definition {key!r} maps to taken Python name {name!r}")
- self._taken.add(name)
- self.def_names[key] = name
- self._building: set[str] = set()
-
- # -- schema helpers -------------------------------------------------------------------------------------
-
- def resolve(self, schema: Schema) -> Schema:
- seen: set[str] = set()
- while "$ref" in schema:
- key = schema["$ref"].removeprefix("#/$defs/")
- if key in seen:
- raise GenerationError(f"Reference cycle at {key}")
- seen.add(key)
- schema = self.definitions[key]
- return schema
-
- def is_null(self, schema: Schema) -> bool:
- return schema.get("type") == "null"
-
- def literal_values(self, schema: Schema) -> list[Any] | None:
- schema = self.resolve(schema)
- if "const" in schema:
- return [schema["const"]]
- if "enum" in schema:
- return list(schema["enum"])
- return None
-
- # -- declarations ---------------------------------------------------------------------------------------
-
- def ensure_definition(self, key: str) -> str:
- name = self.def_names[key]
- if name in self.classes or name in self.aliases or name in self._building:
- return name
- self._building.add(name)
- try:
- self._declare(name, self.definitions[key], source=f"#/$defs/{key}")
- finally:
- self._building.discard(name)
- return name
-
- def _declare(self, name: str, schema: Schema, *, source: str) -> None:
- doc = schema.get("description")
- options = schema.get("anyOf")
- if isinstance(options, list) and len(options) == 1 and self._is_object_model(options[0]):
- # A one-member union is just that member.
- schema = {**options[0], "description": doc or options[0].get("description")}
- if self._is_object_model(schema):
- self.classes[name] = self._class(name, schema, source=source, doc=doc)
- return
- union = self._discriminated_union(name, schema, source=source)
- if union is not None:
- self.aliases[name] = union
- return
- ref = self.type_of(schema, context=name)
- expr = ref.expr if not ref.nullable else f"{ref.expr} | None"
- self.aliases[name] = AliasDecl(name=name, expr=expr, doc=doc, source=source, deps=set(ref.aliases))
-
- def _is_object_model(self, schema: Schema) -> bool:
- if "allOf" in schema and "properties" not in schema:
- return False
- return schema.get("type") == "object" and bool(schema.get("properties"))
-
- def _class(self, name: str, schema: Schema, *, source: str, doc: str | None) -> ClassDecl:
- properties: dict[str, Schema] = schema.get("properties", {})
- required = set(schema.get("required", []))
- fields: list[FieldDecl] = []
- used: set[str] = set()
- for wire, prop in properties.items():
- if prop.get("not") == {}:
- continue # never present
- attr = safe_identifier(snake(wire), reserved=MODEL_RESERVED)
- if attr in used:
- raise GenerationError(f"{name}: field {wire!r} collides with another field as {attr!r}")
- used.add(attr)
- ref = self.type_of(prop, context=f"{name}{pascal(wire)}")
- literal = self.literal_values(prop)
- literal_default = None
- if literal is not None and len(literal) == 1 and isinstance(literal[0], (str, bool, int)):
- literal_default = repr(literal[0])
- fields.append(
- FieldDecl(
- name=attr,
- wire=wire,
- type=ref.expr,
- required=wire in required,
- nullable=ref.nullable,
- doc=prop.get("description") or self._ref_description(prop),
- literal_default=literal_default,
- )
- )
- return ClassDecl(name=name, doc=doc, fields=fields, source=source)
-
- def _ref_description(self, schema: Schema) -> str | None:
- for option in schema.get("anyOf", []):
- if isinstance(option, dict) and option.get("description"):
- return option["description"]
- return None
-
- def _inline_class(self, schema: Schema, context: str) -> str:
- """Declares an inline object as a class named after where it appears (``SessionInfoTime``).
-
- The same shape in the same place reuses one class, so a ``location`` query parameter shared by many
- endpoints becomes a single ``Location`` model.
- """
- shape = _canonical(_without_docs(schema))
- existing = self._inline_by_shape.get(f"{context}:{shape}")
- if existing is not None:
- return existing
- name = context
- if name in self._taken:
- index = 2
- while f"{context}{index}" in self._taken:
- index += 1
- name = f"{context}{index}"
- self._taken.add(name)
- self._inline_by_shape[f"{context}:{shape}"] = name
- self.classes[name] = self._class(name, schema, source=f"inline:{context}", doc=schema.get("description"))
- return name
-
- # -- unions ---------------------------------------------------------------------------------------------
-
- def _member_tags(self, member: Schema) -> dict[str, Any] | None:
- """Literal properties shared by every alternative of a member, or ``None`` if it is not an object."""
- resolved = self.resolve(member)
- options = resolved.get("anyOf")
- if options is not None:
- maps = [self._member_tags(option) for option in options]
- if any(item is None for item in maps):
- return None
- shared: dict[str, Any] = {}
- first = maps[0] or {}
- for key, value in first.items():
- if all(item is not None and item.get(key) == value for item in maps):
- shared[key] = value
- return shared
- if resolved.get("type") != "object":
- return None
- tags: dict[str, Any] = {}
- for key, prop in resolved.get("properties", {}).items():
- if key not in resolved.get("required", []):
- continue
- values = self.literal_values(prop)
- if values is not None and len(values) == 1:
- tags[key] = values[0]
- return tags
-
- def _member_patterns(self, member: Schema, key: str) -> str | None:
- resolved = self.resolve(member)
- prop = resolved.get("properties", {}).get(key)
- if isinstance(prop, dict) and prop.get("type") == "string" and isinstance(prop.get("pattern"), str):
- return prop["pattern"]
- return None
-
- def _discriminated_union(self, name: str, schema: Schema, *, source: str) -> AliasDecl | None:
- options = [option for option in schema.get("anyOf", []) if not self.is_null(option)]
- nullable = len(options) != len(schema.get("anyOf", []))
- if len(options) < 2 or nullable:
- return None
- tag_maps = [self._member_tags(option) for option in options]
- if any(item is None for item in tag_maps):
- return None
- candidates: list[str] = []
- for key in {key for item in tag_maps if item for key in item}:
- values = [item.get(key) if item else None for item in tag_maps]
- patterns = [self._member_patterns(option, key) for option in options]
- known = [value for value in values if value is not None]
- if all(
- value is not None or pattern is not None for value, pattern in zip(values, patterns, strict=True)
- ) and len(set(map(_canonical, known))) == len(known):
- candidates.append(key)
- if not candidates:
- return None
- key = "type" if "type" in candidates else sorted(candidates)[0]
-
- members: list[str] = []
- member_types: list[str] = []
- tags: list[str] = []
- patterns: list[tuple[str, str]] = []
- deps: set[str] = set()
- for index, (option, tag_map) in enumerate(zip(options, tag_maps, strict=True)):
- tag_value = (tag_map or {}).get(key)
- member_context = (
- f"{name}{pascal(_tag_string(tag_value))}" if tag_value is not None else f"{name}Variant{index + 1}"
- )
- ref = self.type_of(option, context=member_context)
- member_types.append(ref.expr)
- deps |= ref.aliases
- value = (tag_map or {}).get(key)
- if value is None:
- pattern = self._member_patterns(option, key)
- assert pattern is not None
- tag = f"pattern:{pattern}"
- patterns.append((pattern, tag))
- else:
- tag = _tag_string(value)
- tags.append(tag)
- members.append(f"Annotated[{ref.expr}, Tag({tag!r})]")
-
- attribute = safe_identifier(snake(key), reserved=MODEL_RESERVED)
- unknown = f"Unknown{name}"
- if unknown in self._taken:
- raise GenerationError(f"Fallback name {unknown!r} is taken")
- self._taken.add(unknown)
- self.classes[unknown] = ClassDecl(
- name=unknown,
- doc=(
- f"A `{name}` variant this client version does not recognize.\n\n"
- "Newer OpenCode servers can add variants. The raw fields are kept and available as attributes "
- "and through `model_extra`."
- ),
- fields=[
- FieldDecl(name=attribute, wire=key, type="Any", required=False, nullable=True, doc=None),
- *self._shared_fields(member_types, exclude=key),
- ],
- source=f"unknown:{name}",
- unknown_for=name,
- )
- members.append(f"Annotated[{unknown}, Tag({UNKNOWN_TAG!r})]")
- pattern_arg = f", patterns={patterns!r}" if patterns else ""
- expr = (
- "Annotated[\n "
- + "\n | ".join(members)
- + f",\n Discriminator(discriminator({key!r}, {attribute!r}, {tags!r}{pattern_arg})),\n]"
- )
- return AliasDecl(name=name, expr=expr, doc=schema.get("description"), source=source, deps=deps)
-
- def _member_classes(self, name: str) -> list[ClassDecl] | None:
- if name in self.classes:
- return [self.classes[name]]
- alias = self.aliases.get(name)
- if alias is None or "Discriminator(" not in alias.expr:
- return None
- result: list[ClassDecl] = []
- for member in re.findall(r"Annotated\[(\w+), Tag\(", alias.expr):
- if member.startswith("Unknown"):
- continue
- classes = self._member_classes(member)
- if classes is None:
- return None
- result += classes
- return result
-
- def _shared_fields(self, member_types: list[str], *, exclude: str) -> list[FieldDecl]:
- """Fields every known member declares with the same type, made optional for the fallback class.
-
- This keeps ``message.id`` type-checking on the whole union, including the unknown variant.
- """
- classes: list[ClassDecl] = []
- for expr in member_types:
- found = self._member_classes(expr)
- if found is None:
- return []
- classes += found
- if not classes:
- return []
- shared: list[FieldDecl] = []
- for candidate in classes[0].fields:
- if candidate.wire == exclude:
- continue
- same = [next((f for f in cls.fields if f.wire == candidate.wire), None) for cls in classes[1:]]
- if all(item is not None and item.type == candidate.type for item in same):
- shared.append(
- FieldDecl(
- name=candidate.name,
- wire=candidate.wire,
- type=candidate.type,
- required=False,
- nullable=True,
- doc=candidate.doc,
- )
- )
- return shared
-
- # -- type expressions -----------------------------------------------------------------------------------
-
- def type_of(self, schema: Schema, *, context: str) -> TypeRef:
- """Returns the Python type expression for a schema. Nullability is reported separately."""
- if "$ref" in schema:
- key = schema["$ref"].removeprefix("#/$defs/")
- name = self.ensure_definition(key)
- return TypeRef(name, aliases={name} if name in self.aliases or name not in self.classes else set())
-
- if "anyOf" in schema or "oneOf" in schema:
- options = schema.get("anyOf") or schema.get("oneOf") or []
- non_null = [option for option in options if not self.is_null(option)]
- nullable = len(non_null) != len(options)
- if not non_null:
- return TypeRef("None", nullable=True)
- if len(non_null) == 1:
- inner = self.type_of(non_null[0], context=context)
- return TypeRef(inner.expr, nullable=nullable or inner.nullable, aliases=inner.aliases)
- literals = [self.literal_values(option) for option in non_null]
- if all(values is not None for values in literals):
- merged = [value for values in literals if values for value in values]
- return TypeRef(self._literal(merged), nullable=nullable)
- union_schema = {"anyOf": non_null}
- # An inline discriminated union gets a name so it can carry its fallback member.
- if all(self._member_tags(option) is not None for option in non_null):
- decl = self._discriminated_union(context, union_schema, source=f"inline:{context}")
- if decl is not None:
- if context in self._taken:
- raise GenerationError(f"Inline union name {context!r} is taken")
- self._taken.add(context)
- self.aliases[context] = decl
- return TypeRef(context, nullable=nullable, aliases={context})
- inline_objects = [option for option in non_null if "$ref" not in option and option.get("properties")]
- parts = [
- self.type_of(
- option,
- # A lone object alternative takes the field's own name: `model: str | ConfigAgentModel`.
- context=context
- if len(inline_objects) == 1 and option is inline_objects[0]
- else f"{context}Option{index + 1}",
- )
- for index, option in enumerate(non_null)
- ]
- exprs: list[str] = []
- aliases: set[str] = set()
- for part in parts:
- aliases |= part.aliases
- if part.expr not in exprs:
- exprs.append(part.expr)
- nullable = nullable or part.nullable
- return TypeRef(" | ".join(exprs), nullable=nullable, aliases=aliases)
-
- if "allOf" in schema:
- merged: Schema = {k: v for k, v in schema.items() if k != "allOf"}
- for part in schema["allOf"]:
- resolved = self.resolve(part)
- if resolved.get("properties"):
- merged.setdefault("properties", {}).update(resolved["properties"])
- merged["required"] = sorted({*merged.get("required", []), *resolved.get("required", [])})
- merged.setdefault("type", resolved.get("type", "object"))
- return self.type_of(merged, context=context)
-
- if "const" in schema:
- return TypeRef(self._literal([schema["const"]]))
- if "enum" in schema:
- return TypeRef(self._literal(list(schema["enum"])))
-
- kind = schema.get("type")
- if isinstance(kind, list):
- options = [{**schema, "type": item} for item in kind]
- return self.type_of({"anyOf": options}, context=context)
- if kind == "string":
- if schema.get("format") == "binary" or schema.get("contentEncoding") == "base64":
- return TypeRef("bytes")
- return TypeRef("str")
- if kind == "integer":
- return TypeRef("int")
- if kind == "number":
- return TypeRef("float")
- if kind == "boolean":
- return TypeRef("bool")
- if kind == "null":
- return TypeRef("None", nullable=True)
- if kind == "array":
- items = schema.get("items")
- if items is None and schema.get("prefixItems"):
- items = {"anyOf": schema["prefixItems"]}
- if items is None or items == {}:
- return TypeRef("list[Any]")
- inner = self.type_of(items, context=f"{context}Item")
- expr = f"{inner.expr} | None" if inner.nullable else inner.expr
- return TypeRef(f"list[{expr}]", aliases=inner.aliases)
- if kind == "object" or "properties" in schema or "additionalProperties" in schema:
- if schema.get("properties"):
- return TypeRef(self._inline_class(schema, context))
- extra = schema.get("additionalProperties")
- if isinstance(extra, dict) and extra:
- inner = self.type_of(extra, context=f"{context}Value")
- expr = f"{inner.expr} | None" if inner.nullable else inner.expr
- return TypeRef(f"dict[str, {expr}]", aliases=inner.aliases)
- return TypeRef("dict[str, Any]")
- if "not" in schema:
- return TypeRef("Any")
- if not schema or set(schema) <= {"description", "title"}:
- return TypeRef("Any")
- raise GenerationError(f"Unsupported schema at {context}: {json.dumps(schema)[:300]}")
-
- def _literal(self, values: list[Any]) -> str:
- unique: list[Any] = []
- for value in values:
- if value not in unique:
- unique.append(value)
- literal = f"Literal[{', '.join(repr(value) for value in unique)}]"
- # Multi-member enums stay open at runtime so new server values do not fail validation.
- return literal if len(unique) == 1 else f"OpenEnum[{literal}]"
-
-
-def _tag_string(value: Any) -> str:
- if isinstance(value, bool):
- return "true" if value else "false"
- return str(value)
-
-
-def ensure_unique(names: list[str]) -> None:
- seen: set[str] = set()
- for name in names:
- if name in seen:
- raise GenerationError(f"Duplicate generated name {name!r}")
- if name in BUILTIN_NAMES:
- raise GenerationError(f"Generated name {name!r} shadows a Python builtin")
- seen.add(name)
diff --git a/fuzz/fuzz_sse.py b/fuzz/fuzz_sse.py
index 132250e..5790658 100644
--- a/fuzz/fuzz_sse.py
+++ b/fuzz/fuzz_sse.py
@@ -8,16 +8,14 @@
import atheris
with atheris.instrument_imports():
- from opencode._streaming import _Decoder, _LineSplitter
+ from opencode._streaming import SSEDecoder
def test_one_input(data: bytes) -> None:
- """Feed arbitrary text through the line splitter and SSE decoder."""
- text = atheris.FuzzedDataProvider(data).ConsumeUnicodeNoSurrogates(len(data))
- splitter = _LineSplitter()
- decoder = _Decoder()
- for line in splitter.feed(text) + splitter.flush():
- decoder.feed_line(line)
+ """Feed arbitrary bytes through the SSE decoder, as the response stream would."""
+ chunks = [data[i : i + 7] for i in range(0, len(data), 7)]
+ for _ in SSEDecoder().iter_bytes(iter(chunks)):
+ pass
if __name__ == "__main__":
diff --git a/pyproject.toml b/pyproject.toml
index 2943ccb..2f5306c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -111,71 +111,139 @@ branch = true
[tool.coverage.report]
exclude_also = ["if TYPE_CHECKING:", "@overload", "raise NotImplementedError"]
+# Lint and type-check settings follow the generated code's (Ferrule, Stainless conventions): most
+# of the package is generated. The hand-written modules (lib/, _rpc, _pty, _events, _server,
+# _service, testing) are held to the same rules.
+[tool.pyright]
+# this enables practically every flag given by pyright.
+# there are a couple of flags that are still disabled by
+# default in strict mode as they are experimental and niche.
+typeCheckingMode = "strict"
+pythonVersion = "3.10"
+
+exclude = [
+ "_dev",
+ ".venv",
+ ".nox",
+ ".git",
+ "codegen",
+ "docs",
+ "fuzz",
+]
+
+reportImplicitOverride = true
+reportOverlappingOverload = false
+
+reportImportCycles = false
+reportPrivateUsage = false
+
+[tool.mypy]
+pretty = true
+show_error_codes = true
+
+# Exclude _files.py because mypy isn't smart enough to apply
+# the correct type narrowing and as this is an internal module
+# it's fine to just use Pyright.
+#
+# We also exclude our `tests` as mypy doesn't always infer
+# types correctly and Pyright will still catch any type errors.
+files = ["src/opencode"]
+exclude = ["src/opencode/_files.py", "_dev/.*.py", "tests/.*"]
+
+strict_equality = true
+implicit_reexport = true
+check_untyped_defs = true
+no_implicit_optional = true
+
+warn_return_any = true
+warn_unreachable = true
+warn_unused_configs = true
+
+# Turn these options off as it could cause conflicts
+# with the Pyright options.
+warn_unused_ignores = false
+warn_redundant_casts = false
+
+disallow_any_generics = true
+disallow_untyped_defs = true
+disallow_untyped_calls = true
+disallow_subclassing_any = true
+disallow_incomplete_defs = true
+disallow_untyped_decorators = true
+cache_fine_grained = true
+
+# By default, mypy reports an error if you assign a value to the result
+# of a function call that doesn't return anything. We do this in our test
+# cases:
+# ```
+# result = ...
+# assert result is None
+# ```
+# Changing this codegen to make mypy happy would increase complexity
+# and would not be worth it.
+disable_error_code = "func-returns-value,overload-cannot-match"
+
+# https://github.com/python/mypy/issues/12162
+[[tool.mypy.overrides]]
+module = "black.files.*"
+ignore_errors = true
+ignore_missing_imports = true
+
+
[tool.ruff]
line-length = 120
+output-format = "grouped"
target-version = "py310"
-src = ["src", "tests", "codegen"]
+
+[tool.ruff.format]
+docstring-code-format = true
[tool.ruff.lint]
select = [
- "B", # bugbear
- "C4", # comprehensions
- "D1", # missing docstrings (public modules only; tests/test_docs_coverage.py covers the private runtime modules)
- "E", # pycodestyle errors
- "F", # pyflakes
- "FA", # future annotations
- "I", # isort
- "PIE", # misc lints
- "PT", # pytest style
- "RUF", # ruff-specific
- "SIM", # simplify
- "T20", # print statements
- "TC", # type-checking imports
- "UP", # pyupgrade
- "W", # pycodestyle warnings
+ # isort
+ "I",
+ # bugbear rules
+ "B",
+ # remove unused imports
+ "F401",
+ # check for missing future annotations
+ "FA102",
+ # bare except statements
+ "E722",
+ # unused arguments
+ "ARG",
+ # print statements
+ "T201",
+ "T203",
+ # misuse of typing.TYPE_CHECKING
+ "TC004",
+ # import rules
+ "TID251",
]
ignore = [
- "D105", # magic methods
- "D107", # __init__: arguments are documented on the class
- "E501", # line length is handled by the formatter
- "RUF022", # __all__ keeps a readable, grouped order
+ # mutable defaults
+ "B006",
+]
+unfixable = [
+ # disable auto fix for print statements
+ "T201",
+ "T203",
]
-[tool.ruff.lint.per-file-ignores]
-# Generated modules import types at runtime because pydantic and the request code evaluate them.
-"src/opencode/types/**" = ["E741", "TC"]
-"src/opencode/resources/**" = ["TC"]
-"src/opencode/_errors.py" = ["TC"]
-# D1 applies to the published package only.
-"codegen/**" = ["T20", "D1"]
-"scripts/**" = ["T20", "D1"]
-"examples/**" = ["T20", "D1"]
-"tests/**" = ["E501", "TC", "D1"]
-"docs/**" = ["D1"]
+extend-safe-fixes = ["FA102"]
+
+[tool.ruff.lint.flake8-tidy-imports.banned-api]
+"functools.lru_cache".msg = "This function does not retain type information for the wrapped function's arguments; The `lru_cache` function from `_utils` should be used instead"
[tool.ruff.lint.isort]
-known-first-party = ["opencode", "codegen"]
+length-sort = true
+length-sort-straight = true
combine-as-imports = true
+extra-standard-library = ["typing_extensions"]
+known-first-party = ["opencode", "tests"]
-[tool.pyright]
-include = ["src", "tests", "examples"]
-pythonVersion = "3.10"
-typeCheckingMode = "strict"
-reportImplicitOverride = "error"
-reportUnnecessaryTypeIgnoreComment = "error"
-
-[[tool.pyright.executionEnvironments]]
-root = "tests"
-# respx ships without complete type information.
-reportPrivateUsage = false
-reportUnknownMemberType = false
-reportUnknownVariableType = false
-reportUnknownArgumentType = false
-reportUnknownLambdaType = false
-
-[tool.mypy]
-python_version = "3.10"
-strict = true
-files = ["src/opencode"]
-warn_unreachable = true
-enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
+[tool.ruff.lint.per-file-ignores]
+"bin/**.py" = ["T201", "T203"]
+"scripts/**.py" = ["T201", "T203"]
+"tests/**.py" = ["T201", "T203"]
+"examples/**.py" = ["T201", "T203"]
diff --git a/src/opencode/.generated.json b/src/opencode/.generated.json
deleted file mode 100644
index ce493f2..0000000
--- a/src/opencode/.generated.json
+++ /dev/null
@@ -1,39 +0,0 @@
-[
- "api.md",
- "src/opencode/__init__.py",
- "src/opencode/_errors.py",
- "src/opencode/resources/__init__.py",
- "src/opencode/resources/agent.py",
- "src/opencode/resources/command.py",
- "src/opencode/resources/config.py",
- "src/opencode/resources/credential.py",
- "src/opencode/resources/debug.py",
- "src/opencode/resources/event.py",
- "src/opencode/resources/experimental.py",
- "src/opencode/resources/file.py",
- "src/opencode/resources/form.py",
- "src/opencode/resources/generate.py",
- "src/opencode/resources/integration.py",
- "src/opencode/resources/location.py",
- "src/opencode/resources/mcp.py",
- "src/opencode/resources/message.py",
- "src/opencode/resources/migration.py",
- "src/opencode/resources/model.py",
- "src/opencode/resources/permission.py",
- "src/opencode/resources/plugin.py",
- "src/opencode/resources/project.py",
- "src/opencode/resources/provider.py",
- "src/opencode/resources/pty.py",
- "src/opencode/resources/reference.py",
- "src/opencode/resources/rpc.py",
- "src/opencode/resources/server.py",
- "src/opencode/resources/session.py",
- "src/opencode/resources/shell.py",
- "src/opencode/resources/skill.py",
- "src/opencode/resources/vcs.py",
- "src/opencode/resources/voice.py",
- "src/opencode/resources/websearch.py",
- "src/opencode/resources/worktree.py",
- "src/opencode/types/__init__.py",
- "src/opencode/types/_generated.py"
-]
diff --git a/src/opencode/_aiohttp.py b/src/opencode/_aiohttp.py
deleted file mode 100644
index 476756b..0000000
--- a/src/opencode/_aiohttp.py
+++ /dev/null
@@ -1,44 +0,0 @@
-"""Optional aiohttp-backed HTTP client for `AsyncOpencode`.
-
-Install the extra with ``pip install 'opencode-python-sdk[aiohttp]'`` and pass the client in::
-
- async with AsyncOpencode(http_client=DefaultAioHttpClient()) as client:
- ...
-
-The SDK still speaks httpx: ``httpx-aiohttp`` swaps the transport underneath, so retries, timeouts and event
-streams behave the same. aiohttp only runs on asyncio, so this client does not work under trio.
-"""
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING, Any
-
-import httpx
-
-from . import _constants as constants
-
-if TYPE_CHECKING:
- _Base = httpx.AsyncClient
-else:
- try:
- from httpx_aiohttp import HttpxAiohttpClient as _Base
- except ImportError: # pragma: no cover - only without the extra
- _Base = httpx.AsyncClient
-
-
-class DefaultAioHttpClient(_Base):
- """aiohttp-backed `httpx.AsyncClient` with the SDK's default connection limits and redirect handling.
-
- Pass it to `AsyncOpencode` as ``http_client``. Keyword arguments go to `httpx.AsyncClient`. The SDK does not
- close a client it was given, so close it yourself (``async with`` it) when you are done. Creating one without
- the ``aiohttp`` extra installed raises `RuntimeError`.
- """
-
- def __init__(self, **kwargs: Any) -> None:
- if _Base is httpx.AsyncClient:
- raise RuntimeError(
- "DefaultAioHttpClient needs the aiohttp extra: pip install 'opencode-python-sdk[aiohttp]'"
- )
- kwargs.setdefault("limits", constants.DEFAULT_CONNECTION_LIMITS)
- kwargs.setdefault("follow_redirects", True)
- super().__init__(**kwargs)
diff --git a/src/opencode/_logs.py b/src/opencode/_logs.py
deleted file mode 100644
index 687de12..0000000
--- a/src/opencode/_logs.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""``OPENCODE_LOG`` support: configure the ``opencode`` logger from the environment."""
-
-from __future__ import annotations
-
-import logging
-import os
-
-logger = logging.getLogger("opencode")
-
-_LEVELS = {"debug": logging.DEBUG, "info": logging.INFO}
-
-
-def setup_logging() -> None:
- """Attach a stderr handler to the ``opencode`` logger when ``OPENCODE_LOG`` is ``debug`` or ``info``.
-
- Does nothing when the variable is unset or has any other value. The OpenCode CLI reads
- ``OPENCODE_LOG_LEVEL``, not ``OPENCODE_LOG``, so the two do not clash.
- """
- level = _LEVELS.get(os.environ.get("OPENCODE_LOG", "").strip().lower())
- if level is None:
- return
- if not any(getattr(h, "_opencode_env", False) for h in logger.handlers):
- handler = logging.StreamHandler()
- handler.setFormatter(
- logging.Formatter("[%(asctime)s - %(name)s:%(lineno)d - %(levelname)s] %(message)s", "%Y-%m-%d %H:%M:%S")
- )
- handler._opencode_env = True # type: ignore[attr-defined]
- logger.addHandler(handler)
- logger.setLevel(level)
diff --git a/src/opencode/_pagination.py b/src/opencode/_pagination.py
deleted file mode 100644
index 484cfbe..0000000
--- a/src/opencode/_pagination.py
+++ /dev/null
@@ -1,206 +0,0 @@
-"""Cursor pagination.
-
-OpenCode list endpoints return ``{"data": [...], "cursor": {"previous": ..., "next": ...}}``. A page iterates over
-every item of every following page, fetching lazily::
-
- for message in client.message.list("ses_..."):
- ...
-
-Use :meth:`SyncCursorPage.iter_pages` or the cursor properties for manual control.
-"""
-
-from __future__ import annotations
-
-import dataclasses
-from typing import TYPE_CHECKING, Any, Generic, TypeVar
-
-if TYPE_CHECKING:
- from collections.abc import AsyncIterator, Generator, Iterator
-
- from ._base_client import AsyncAPIClient, RequestOptions, SyncAPIClient
-
-__all__ = ["AsyncCursorPage", "AsyncPaginator", "SyncCursorPage"]
-
-_T = TypeVar("_T")
-
-
-def _page_parts(payload: Any) -> tuple[list[Any], str | None, str | None]:
- cursor = payload.cursor
- return list(payload.data), getattr(cursor, "next", None), getattr(cursor, "previous", None)
-
-
-def _follow(options: RequestOptions, cursor: str) -> RequestOptions:
- query = {key: value for key, value in options.query.items() if key != "order"}
- # The server rejects `order` together with a cursor: the cursor already encodes the direction.
- query["cursor"] = cursor
- return dataclasses.replace(options, query=query)
-
-
-class _PageBase(Generic[_T]):
- data: list[_T]
- """Items on this page."""
- next_cursor: str | None
- """Cursor for the following page, or ``None`` on the last page."""
- previous_cursor: str | None
- """Cursor for the preceding page, or ``None`` on the first page."""
-
- def __init__(
- self, *, data: list[_T], next_cursor: str | None, previous_cursor: str | None, options: RequestOptions
- ) -> None:
- self.data = data
- self.next_cursor = next_cursor
- self.previous_cursor = previous_cursor
- self._options = options
-
- def has_next_page(self) -> bool:
- return bool(self.next_cursor)
-
- def __len__(self) -> int:
- return len(self.data)
-
-
-class SyncCursorPage(_PageBase[_T]):
- """One page of results. Iterating it walks this page and every page after it."""
-
- def __init__(
- self,
- *,
- client: SyncAPIClient,
- data: list[_T],
- next_cursor: str | None,
- previous_cursor: str | None,
- options: RequestOptions,
- response_type: Any,
- ) -> None:
- super().__init__(data=data, next_cursor=next_cursor, previous_cursor=previous_cursor, options=options)
- self._client = client
- self._response_type = response_type
-
- @classmethod
- def fetch(cls, client: SyncAPIClient, options: RequestOptions, response_type: Any) -> SyncCursorPage[Any]:
- payload = client.request(options, response_type=response_type)
- return cls.build(client, options, response_type, payload)
-
- @classmethod
- def build(
- cls, client: SyncAPIClient, options: RequestOptions, response_type: Any, payload: Any
- ) -> SyncCursorPage[Any]:
- """Builds a page from an already parsed response."""
- data, next_cursor, previous_cursor = _page_parts(payload)
- return cls(
- client=client,
- data=data,
- next_cursor=next_cursor,
- previous_cursor=previous_cursor,
- options=options,
- response_type=response_type,
- )
-
- def next_page(self) -> SyncCursorPage[_T]:
- """Fetches the following page. Raises ``StopIteration`` on the last page."""
- if not self.next_cursor:
- raise StopIteration("No more pages")
- return SyncCursorPage.fetch(self._client, _follow(self._options, self.next_cursor), self._response_type)
-
- def previous_page(self) -> SyncCursorPage[_T]:
- """Fetches the preceding page. Raises ``StopIteration`` on the first page."""
- if not self.previous_cursor:
- raise StopIteration("No previous page")
- return SyncCursorPage.fetch(self._client, _follow(self._options, self.previous_cursor), self._response_type)
-
- def iter_pages(self) -> Generator[SyncCursorPage[_T], None, None]:
- """Yields this page and then each following page."""
- page: SyncCursorPage[_T] = self
- yield page
- while page.has_next_page():
- page = page.next_page()
- yield page
-
- def __iter__(self) -> Iterator[_T]:
- for page in self.iter_pages():
- yield from page.data
-
-
-class AsyncCursorPage(_PageBase[_T]):
- """One page of results. ``async for`` walks this page and every page after it."""
-
- def __init__(
- self,
- *,
- client: AsyncAPIClient,
- data: list[_T],
- next_cursor: str | None,
- previous_cursor: str | None,
- options: RequestOptions,
- response_type: Any,
- ) -> None:
- super().__init__(data=data, next_cursor=next_cursor, previous_cursor=previous_cursor, options=options)
- self._client = client
- self._response_type = response_type
-
- @classmethod
- async def fetch(cls, client: AsyncAPIClient, options: RequestOptions, response_type: Any) -> AsyncCursorPage[Any]:
- payload = await client.request(options, response_type=response_type)
- return cls.build(client, options, response_type, payload)
-
- @classmethod
- def build(
- cls, client: AsyncAPIClient, options: RequestOptions, response_type: Any, payload: Any
- ) -> AsyncCursorPage[Any]:
- """Builds a page from an already parsed response."""
- data, next_cursor, previous_cursor = _page_parts(payload)
- return cls(
- client=client,
- data=data,
- next_cursor=next_cursor,
- previous_cursor=previous_cursor,
- options=options,
- response_type=response_type,
- )
-
- async def next_page(self) -> AsyncCursorPage[_T]:
- """Fetches the following page. Raises ``StopAsyncIteration`` on the last page."""
- if not self.next_cursor:
- raise StopAsyncIteration("No more pages")
- return await AsyncCursorPage.fetch(self._client, _follow(self._options, self.next_cursor), self._response_type)
-
- async def previous_page(self) -> AsyncCursorPage[_T]:
- """Fetches the preceding page. Raises ``StopAsyncIteration`` on the first page."""
- if not self.previous_cursor:
- raise StopAsyncIteration("No previous page")
- return await AsyncCursorPage.fetch(
- self._client, _follow(self._options, self.previous_cursor), self._response_type
- )
-
- async def iter_pages(self) -> AsyncIterator[AsyncCursorPage[_T]]:
- """Yields this page and then each following page."""
- page: AsyncCursorPage[_T] = self
- yield page
- while page.has_next_page():
- page = await page.next_page()
- yield page
-
- async def __aiter__(self) -> AsyncIterator[_T]:
- async for page in self.iter_pages():
- for item in page.data:
- yield item
-
-
-class AsyncPaginator(Generic[_T]):
- """Returned by async list methods.
-
- ``await`` it for the first page, or ``async for`` over it to walk every item across pages.
- """
-
- def __init__(self, client: AsyncAPIClient, options: RequestOptions, response_type: Any) -> None:
- self._client = client
- self._options = options
- self._response_type = response_type
-
- def __await__(self) -> Generator[Any, None, AsyncCursorPage[_T]]:
- return AsyncCursorPage.fetch(self._client, self._options, self._response_type).__await__()
-
- async def __aiter__(self) -> AsyncIterator[_T]:
- page: AsyncCursorPage[_T] = await self
- async for item in page:
- yield item
diff --git a/src/opencode/_unions.py b/src/opencode/_unions.py
deleted file mode 100644
index 84fcef2..0000000
--- a/src/opencode/_unions.py
+++ /dev/null
@@ -1,46 +0,0 @@
-"""Runtime support for discriminated unions in the generated models."""
-
-from __future__ import annotations
-
-import re
-from typing import TYPE_CHECKING, Any, cast
-
-if TYPE_CHECKING:
- from collections.abc import Callable, Iterable
-
-__all__ = ["UNKNOWN_TAG", "discriminator"]
-
-UNKNOWN_TAG = "__unknown__"
-"""Tag of the fallback member that receives variants this client version does not know."""
-
-
-def discriminator(
- key: str,
- attribute: str,
- tags: Iterable[str],
- patterns: Iterable[tuple[str, str]] = (),
-) -> Callable[[Any], str]:
- """Builds a ``pydantic.Discriminator`` function.
-
- ``key`` is the wire field and ``attribute`` the model attribute that hold the tag. The tag selects a member by
- exact value, then by regular expression. Anything else selects the ``UNKNOWN_TAG`` member, so a newer server can
- add variants without breaking older clients.
- """
- known = frozenset(tags)
- compiled = [(re.compile(pattern), member) for pattern, member in patterns]
-
- def select(value: Any) -> str:
- tag: object = (
- cast("dict[str, Any]", value).get(key) if isinstance(value, dict) else getattr(value, attribute, None)
- )
- if isinstance(tag, bool):
- tag = "true" if tag else "false"
- if isinstance(tag, str):
- if tag in known:
- return tag
- for pattern, member in compiled:
- if pattern.search(tag):
- return member
- return UNKNOWN_TAG
-
- return select
diff --git a/src/opencode/resources/debug.py b/src/opencode/resources/debug.py
deleted file mode 100644
index 6df94ba..0000000
--- a/src/opencode/resources/debug.py
+++ /dev/null
@@ -1,314 +0,0 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``debug`` resource."""
-
-from __future__ import annotations
-
-import builtins
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
-
-from .._base_client import RequestOptions
-from .._resource import AsyncAPIResource, SyncAPIResource
-from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
-)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- Location,
- LocationPublicRef,
- )
-
-__all__ = ["DebugResource", "AsyncDebugResource"]
-
-
-class DebugLocationResource(SyncAPIResource):
- """Operations under ``client.debug.location`` (OpenCode ``debug.location``)."""
-
- @cached_property
- def with_raw_response(self) -> DebugLocationResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return DebugLocationResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> DebugLocationResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return DebugLocationResourceWithStreamingResponse(self)
-
- def list(
- self,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[LocationPublicRef]:
- """List loaded locations.
-
- List locations currently loaded by the server.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
-
- ``GET /api/debug/location``
- """
- return cast(
- "builtins.list[LocationPublicRef]",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/debug/location",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[LocationPublicRef]",
- ),
- )
-
- def evict(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Evict a loaded location.
-
- Dispose the requested location's cached services so its next use boots them fresh.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
-
- ``DELETE /api/debug/location``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="DELETE",
- path="/api/debug/location",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class DebugLocationResourceWithRawResponse:
- """``client.debug.location.with_raw_response``."""
-
- def __init__(self, resource: DebugLocationResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
- self.evict = to_raw_response_wrapper(resource.evict)
-
-
-class DebugLocationResourceWithStreamingResponse:
- """``client.debug.location.with_streaming_response``."""
-
- def __init__(self, resource: DebugLocationResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
- self.evict = to_streamed_response_wrapper(resource.evict)
-
-
-class DebugResource(SyncAPIResource):
- """Operations under ``client.debug`` (OpenCode ``debug``)."""
-
- @cached_property
- def location(self) -> DebugLocationResource:
- """Operations under ``client.debug.location``."""
- return DebugLocationResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> DebugResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return DebugResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> DebugResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return DebugResourceWithStreamingResponse(self)
-
-
-class DebugResourceWithRawResponse:
- """``client.debug.with_raw_response``."""
-
- def __init__(self, resource: DebugResource) -> None:
- self._resource = resource
-
- @cached_property
- def location(self) -> DebugLocationResourceWithRawResponse:
- """``client.debug.location.with_raw_response``."""
- return DebugLocationResourceWithRawResponse(self._resource.location)
-
-
-class DebugResourceWithStreamingResponse:
- """``client.debug.with_streaming_response``."""
-
- def __init__(self, resource: DebugResource) -> None:
- self._resource = resource
-
- @cached_property
- def location(self) -> DebugLocationResourceWithStreamingResponse:
- """``client.debug.location.with_streaming_response``."""
- return DebugLocationResourceWithStreamingResponse(self._resource.location)
-
-
-class AsyncDebugLocationResource(AsyncAPIResource):
- """Operations under ``client.debug.location`` (OpenCode ``debug.location``)."""
-
- @cached_property
- def with_raw_response(self) -> AsyncDebugLocationResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncDebugLocationResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncDebugLocationResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncDebugLocationResourceWithStreamingResponse(self)
-
- async def list(
- self,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[LocationPublicRef]:
- """List loaded locations.
-
- List locations currently loaded by the server.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
-
- ``GET /api/debug/location``
- """
- return cast(
- "builtins.list[LocationPublicRef]",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/debug/location",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[LocationPublicRef]",
- ),
- )
-
- async def evict(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Evict a loaded location.
-
- Dispose the requested location's cached services so its next use boots them fresh.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
-
- ``DELETE /api/debug/location``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="DELETE",
- path="/api/debug/location",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class AsyncDebugLocationResourceWithRawResponse:
- """``client.debug.location.with_raw_response``."""
-
- def __init__(self, resource: AsyncDebugLocationResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
- self.evict = async_to_raw_response_wrapper(resource.evict)
-
-
-class AsyncDebugLocationResourceWithStreamingResponse:
- """``client.debug.location.with_streaming_response``."""
-
- def __init__(self, resource: AsyncDebugLocationResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.evict = async_to_streamed_response_wrapper(resource.evict)
-
-
-class AsyncDebugResource(AsyncAPIResource):
- """Operations under ``client.debug`` (OpenCode ``debug``)."""
-
- @cached_property
- def location(self) -> AsyncDebugLocationResource:
- """Operations under ``client.debug.location``."""
- return AsyncDebugLocationResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> AsyncDebugResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncDebugResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncDebugResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncDebugResourceWithStreamingResponse(self)
-
-
-class AsyncDebugResourceWithRawResponse:
- """``client.debug.with_raw_response``."""
-
- def __init__(self, resource: AsyncDebugResource) -> None:
- self._resource = resource
-
- @cached_property
- def location(self) -> AsyncDebugLocationResourceWithRawResponse:
- """``client.debug.location.with_raw_response``."""
- return AsyncDebugLocationResourceWithRawResponse(self._resource.location)
-
-
-class AsyncDebugResourceWithStreamingResponse:
- """``client.debug.with_streaming_response``."""
-
- def __init__(self, resource: AsyncDebugResource) -> None:
- self._resource = resource
-
- @cached_property
- def location(self) -> AsyncDebugLocationResourceWithStreamingResponse:
- """``client.debug.location.with_streaming_response``."""
- return AsyncDebugLocationResourceWithStreamingResponse(self._resource.location)
diff --git a/src/opencode/resources/experimental.py b/src/opencode/resources/experimental.py
deleted file mode 100644
index ae3049f..0000000
--- a/src/opencode/resources/experimental.py
+++ /dev/null
@@ -1,939 +0,0 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``experimental`` resource."""
-
-from __future__ import annotations
-
-import builtins
-from collections.abc import Mapping, Sequence
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
-
-from .._base_client import RequestOptions
-from .._pty import AsyncPersistentPtyAttachMixin, PersistentPtyAttachMixin
-from .._qs import encode_path_segment
-from .._resource import AsyncAPIResource, SyncAPIResource
-from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
-)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- ExperimentalPersistentPtyCreateSize,
- ExperimentalPersistentPtyHandoffResponse,
- ExperimentalPersistentPtyUpdateSize,
- PersistentPtyInfo,
- PersistentPtyReadLines,
- PersistentPtyReadResult,
- PersistentPtySnapshot,
- PtyID,
- PtyTicketConnectToken,
- SessionID,
- )
-
-__all__ = ["ExperimentalResource", "AsyncExperimentalResource"]
-
-
-class ExperimentalPersistentPtyResource(PersistentPtyAttachMixin):
- """Operations under ``client.experimental.persistent_pty`` (OpenCode ``experimental.persistentPty``)."""
-
- @cached_property
- def with_raw_response(self) -> ExperimentalPersistentPtyResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return ExperimentalPersistentPtyResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> ExperimentalPersistentPtyResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return ExperimentalPersistentPtyResourceWithStreamingResponse(self)
-
- def read(
- self,
- session_id: SessionID,
- *,
- lines: PersistentPtyReadLines | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PersistentPtyReadResult | None:
- """Read the session's most recently controlled terminal.
-
- Read the last physical rows without changing selection or taking control. Omitted lines uses the live terminal height; larger counts include retained history. Blank rows are preserved. Screen dimensions and cursor remain relative to the live screen. Returns null when no current terminal exists. Selection is server-local and resets on restart. Experimental: may change without compatibility guarantees.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/experimental/session/:sessionID/terminal/read``
- """
- return cast(
- "PersistentPtyReadResult | None",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/terminal/read",
- query={"lines": lines},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PersistentPtyReadResult | None",
- unwrap=True,
- ),
- )
-
- def list(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[PersistentPtyInfo]:
- """This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/experimental/session/:sessionID/terminal``
- """
- return cast(
- "builtins.list[PersistentPtyInfo]",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/terminal",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[PersistentPtyInfo]",
- unwrap=True,
- ),
- )
-
- def create(
- self,
- session_id: SessionID,
- *,
- args: Sequence[str],
- title: str,
- env: Mapping[str, str],
- command: str | NotGiven = NOT_GIVEN,
- cwd: str | NotGiven = NOT_GIVEN,
- size: ExperimentalPersistentPtyCreateSize | NotGiven = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PersistentPtyInfo:
- """This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ServiceUnavailableError: The server answered 503.
-
- ``POST /api/experimental/session/:sessionID/terminal``
- """
- return cast(
- "PersistentPtyInfo",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/terminal",
- body={"command": command, "args": args, "cwd": cwd, "title": title, "env": env, "size": size},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PersistentPtyInfo",
- unwrap=True,
- ),
- )
-
- def shutdown(
- self,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ServiceUnavailableError: The server answered 503.
-
- ``POST /api/experimental/persistent-pty/shutdown``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/experimental/persistent-pty/shutdown",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def handoff(
- self,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> ExperimentalPersistentPtyHandoffResponse:
- """This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ServiceUnavailableError: The server answered 503.
-
- ``POST /api/experimental/persistent-pty/handoff``
- """
- return cast(
- "ExperimentalPersistentPtyHandoffResponse",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/experimental/persistent-pty/handoff",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ExperimentalPersistentPtyHandoffResponse",
- ),
- )
-
- def get(
- self,
- pty_id: PtyID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PersistentPtyInfo:
- """This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- PtyNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/experimental/persistent-pty/:ptyID``
- """
- return cast(
- "PersistentPtyInfo",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/experimental/persistent-pty/{encode_path_segment(pty_id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PersistentPtyInfo",
- unwrap=True,
- ),
- )
-
- def update(
- self,
- pty_id: PtyID,
- *,
- size: ExperimentalPersistentPtyUpdateSize,
- attachment_id: str | NotGiven = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PersistentPtyInfo:
- """This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- PtyNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``PUT /api/experimental/persistent-pty/:ptyID``
- """
- return cast(
- "PersistentPtyInfo",
- self._client.request(
- RequestOptions(
- method="PUT",
- path=f"/api/experimental/persistent-pty/{encode_path_segment(pty_id)}",
- body={"attachmentID": attachment_id, "size": size},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PersistentPtyInfo",
- unwrap=True,
- ),
- )
-
- def snapshot(
- self,
- pty_id: PtyID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PersistentPtySnapshot:
- """This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- PtyNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/experimental/persistent-pty/:ptyID/snapshot``
- """
- return cast(
- "PersistentPtySnapshot",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/experimental/persistent-pty/{encode_path_segment(pty_id)}/snapshot",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PersistentPtySnapshot",
- unwrap=True,
- ),
- )
-
- def remove(
- self,
- pty_id: PtyID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- PtyNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``DELETE /api/experimental/persistent-pty/:ptyID``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/experimental/persistent-pty/{encode_path_segment(pty_id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def connect_token(
- self,
- pty_id: PtyID,
- *,
- x_opencode_ticket: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PtyTicketConnectToken:
- """This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ForbiddenError: The server answered 403.
- PtyNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``POST /api/experimental/persistent-pty/:ptyID/connect-token``
- """
- return cast(
- "PtyTicketConnectToken",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/experimental/persistent-pty/{encode_path_segment(pty_id)}/connect-token",
- headers={"x-opencode-ticket": x_opencode_ticket},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PtyTicketConnectToken",
- unwrap=True,
- ),
- )
-
-
-class ExperimentalPersistentPtyResourceWithRawResponse:
- """``client.experimental.persistent_pty.with_raw_response``."""
-
- def __init__(self, resource: ExperimentalPersistentPtyResource) -> None:
- self._resource = resource
- self.read = to_raw_response_wrapper(resource.read)
- self.list = to_raw_response_wrapper(resource.list)
- self.create = to_raw_response_wrapper(resource.create)
- self.shutdown = to_raw_response_wrapper(resource.shutdown)
- self.handoff = to_raw_response_wrapper(resource.handoff)
- self.get = to_raw_response_wrapper(resource.get)
- self.update = to_raw_response_wrapper(resource.update)
- self.snapshot = to_raw_response_wrapper(resource.snapshot)
- self.remove = to_raw_response_wrapper(resource.remove)
- self.connect_token = to_raw_response_wrapper(resource.connect_token)
-
-
-class ExperimentalPersistentPtyResourceWithStreamingResponse:
- """``client.experimental.persistent_pty.with_streaming_response``."""
-
- def __init__(self, resource: ExperimentalPersistentPtyResource) -> None:
- self._resource = resource
- self.read = to_streamed_response_wrapper(resource.read)
- self.list = to_streamed_response_wrapper(resource.list)
- self.create = to_streamed_response_wrapper(resource.create)
- self.shutdown = to_streamed_response_wrapper(resource.shutdown)
- self.handoff = to_streamed_response_wrapper(resource.handoff)
- self.get = to_streamed_response_wrapper(resource.get)
- self.update = to_streamed_response_wrapper(resource.update)
- self.snapshot = to_streamed_response_wrapper(resource.snapshot)
- self.remove = to_streamed_response_wrapper(resource.remove)
- self.connect_token = to_streamed_response_wrapper(resource.connect_token)
-
-
-class ExperimentalResource(SyncAPIResource):
- """Operations under ``client.experimental`` (OpenCode ``experimental``)."""
-
- @cached_property
- def persistent_pty(self) -> ExperimentalPersistentPtyResource:
- """Operations under ``client.experimental.persistent_pty``."""
- return ExperimentalPersistentPtyResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> ExperimentalResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return ExperimentalResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> ExperimentalResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return ExperimentalResourceWithStreamingResponse(self)
-
-
-class ExperimentalResourceWithRawResponse:
- """``client.experimental.with_raw_response``."""
-
- def __init__(self, resource: ExperimentalResource) -> None:
- self._resource = resource
-
- @cached_property
- def persistent_pty(self) -> ExperimentalPersistentPtyResourceWithRawResponse:
- """``client.experimental.persistent_pty.with_raw_response``."""
- return ExperimentalPersistentPtyResourceWithRawResponse(self._resource.persistent_pty)
-
-
-class ExperimentalResourceWithStreamingResponse:
- """``client.experimental.with_streaming_response``."""
-
- def __init__(self, resource: ExperimentalResource) -> None:
- self._resource = resource
-
- @cached_property
- def persistent_pty(self) -> ExperimentalPersistentPtyResourceWithStreamingResponse:
- """``client.experimental.persistent_pty.with_streaming_response``."""
- return ExperimentalPersistentPtyResourceWithStreamingResponse(self._resource.persistent_pty)
-
-
-class AsyncExperimentalPersistentPtyResource(AsyncPersistentPtyAttachMixin):
- """Operations under ``client.experimental.persistent_pty`` (OpenCode ``experimental.persistentPty``)."""
-
- @cached_property
- def with_raw_response(self) -> AsyncExperimentalPersistentPtyResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncExperimentalPersistentPtyResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncExperimentalPersistentPtyResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncExperimentalPersistentPtyResourceWithStreamingResponse(self)
-
- async def read(
- self,
- session_id: SessionID,
- *,
- lines: PersistentPtyReadLines | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PersistentPtyReadResult | None:
- """Read the session's most recently controlled terminal.
-
- Read the last physical rows without changing selection or taking control. Omitted lines uses the live terminal height; larger counts include retained history. Blank rows are preserved. Screen dimensions and cursor remain relative to the live screen. Returns null when no current terminal exists. Selection is server-local and resets on restart. Experimental: may change without compatibility guarantees.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/experimental/session/:sessionID/terminal/read``
- """
- return cast(
- "PersistentPtyReadResult | None",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/terminal/read",
- query={"lines": lines},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PersistentPtyReadResult | None",
- unwrap=True,
- ),
- )
-
- async def list(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[PersistentPtyInfo]:
- """This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/experimental/session/:sessionID/terminal``
- """
- return cast(
- "builtins.list[PersistentPtyInfo]",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/terminal",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[PersistentPtyInfo]",
- unwrap=True,
- ),
- )
-
- async def create(
- self,
- session_id: SessionID,
- *,
- args: Sequence[str],
- title: str,
- env: Mapping[str, str],
- command: str | NotGiven = NOT_GIVEN,
- cwd: str | NotGiven = NOT_GIVEN,
- size: ExperimentalPersistentPtyCreateSize | NotGiven = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PersistentPtyInfo:
- """This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ServiceUnavailableError: The server answered 503.
-
- ``POST /api/experimental/session/:sessionID/terminal``
- """
- return cast(
- "PersistentPtyInfo",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/terminal",
- body={"command": command, "args": args, "cwd": cwd, "title": title, "env": env, "size": size},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PersistentPtyInfo",
- unwrap=True,
- ),
- )
-
- async def shutdown(
- self,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ServiceUnavailableError: The server answered 503.
-
- ``POST /api/experimental/persistent-pty/shutdown``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/experimental/persistent-pty/shutdown",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def handoff(
- self,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> ExperimentalPersistentPtyHandoffResponse:
- """This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ServiceUnavailableError: The server answered 503.
-
- ``POST /api/experimental/persistent-pty/handoff``
- """
- return cast(
- "ExperimentalPersistentPtyHandoffResponse",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/experimental/persistent-pty/handoff",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="ExperimentalPersistentPtyHandoffResponse",
- ),
- )
-
- async def get(
- self,
- pty_id: PtyID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PersistentPtyInfo:
- """This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- PtyNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/experimental/persistent-pty/:ptyID``
- """
- return cast(
- "PersistentPtyInfo",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/experimental/persistent-pty/{encode_path_segment(pty_id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PersistentPtyInfo",
- unwrap=True,
- ),
- )
-
- async def update(
- self,
- pty_id: PtyID,
- *,
- size: ExperimentalPersistentPtyUpdateSize,
- attachment_id: str | NotGiven = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PersistentPtyInfo:
- """This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- PtyNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``PUT /api/experimental/persistent-pty/:ptyID``
- """
- return cast(
- "PersistentPtyInfo",
- await self._client.request(
- RequestOptions(
- method="PUT",
- path=f"/api/experimental/persistent-pty/{encode_path_segment(pty_id)}",
- body={"attachmentID": attachment_id, "size": size},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PersistentPtyInfo",
- unwrap=True,
- ),
- )
-
- async def snapshot(
- self,
- pty_id: PtyID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PersistentPtySnapshot:
- """This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- PtyNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/experimental/persistent-pty/:ptyID/snapshot``
- """
- return cast(
- "PersistentPtySnapshot",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/experimental/persistent-pty/{encode_path_segment(pty_id)}/snapshot",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PersistentPtySnapshot",
- unwrap=True,
- ),
- )
-
- async def remove(
- self,
- pty_id: PtyID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- PtyNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``DELETE /api/experimental/persistent-pty/:ptyID``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/experimental/persistent-pty/{encode_path_segment(pty_id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def connect_token(
- self,
- pty_id: PtyID,
- *,
- x_opencode_ticket: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PtyTicketConnectToken:
- """This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ForbiddenError: The server answered 403.
- PtyNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``POST /api/experimental/persistent-pty/:ptyID/connect-token``
- """
- return cast(
- "PtyTicketConnectToken",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/experimental/persistent-pty/{encode_path_segment(pty_id)}/connect-token",
- headers={"x-opencode-ticket": x_opencode_ticket},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PtyTicketConnectToken",
- unwrap=True,
- ),
- )
-
-
-class AsyncExperimentalPersistentPtyResourceWithRawResponse:
- """``client.experimental.persistent_pty.with_raw_response``."""
-
- def __init__(self, resource: AsyncExperimentalPersistentPtyResource) -> None:
- self._resource = resource
- self.read = async_to_raw_response_wrapper(resource.read)
- self.list = async_to_raw_response_wrapper(resource.list)
- self.create = async_to_raw_response_wrapper(resource.create)
- self.shutdown = async_to_raw_response_wrapper(resource.shutdown)
- self.handoff = async_to_raw_response_wrapper(resource.handoff)
- self.get = async_to_raw_response_wrapper(resource.get)
- self.update = async_to_raw_response_wrapper(resource.update)
- self.snapshot = async_to_raw_response_wrapper(resource.snapshot)
- self.remove = async_to_raw_response_wrapper(resource.remove)
- self.connect_token = async_to_raw_response_wrapper(resource.connect_token)
-
-
-class AsyncExperimentalPersistentPtyResourceWithStreamingResponse:
- """``client.experimental.persistent_pty.with_streaming_response``."""
-
- def __init__(self, resource: AsyncExperimentalPersistentPtyResource) -> None:
- self._resource = resource
- self.read = async_to_streamed_response_wrapper(resource.read)
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.create = async_to_streamed_response_wrapper(resource.create)
- self.shutdown = async_to_streamed_response_wrapper(resource.shutdown)
- self.handoff = async_to_streamed_response_wrapper(resource.handoff)
- self.get = async_to_streamed_response_wrapper(resource.get)
- self.update = async_to_streamed_response_wrapper(resource.update)
- self.snapshot = async_to_streamed_response_wrapper(resource.snapshot)
- self.remove = async_to_streamed_response_wrapper(resource.remove)
- self.connect_token = async_to_streamed_response_wrapper(resource.connect_token)
-
-
-class AsyncExperimentalResource(AsyncAPIResource):
- """Operations under ``client.experimental`` (OpenCode ``experimental``)."""
-
- @cached_property
- def persistent_pty(self) -> AsyncExperimentalPersistentPtyResource:
- """Operations under ``client.experimental.persistent_pty``."""
- return AsyncExperimentalPersistentPtyResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> AsyncExperimentalResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncExperimentalResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncExperimentalResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncExperimentalResourceWithStreamingResponse(self)
-
-
-class AsyncExperimentalResourceWithRawResponse:
- """``client.experimental.with_raw_response``."""
-
- def __init__(self, resource: AsyncExperimentalResource) -> None:
- self._resource = resource
-
- @cached_property
- def persistent_pty(self) -> AsyncExperimentalPersistentPtyResourceWithRawResponse:
- """``client.experimental.persistent_pty.with_raw_response``."""
- return AsyncExperimentalPersistentPtyResourceWithRawResponse(self._resource.persistent_pty)
-
-
-class AsyncExperimentalResourceWithStreamingResponse:
- """``client.experimental.with_streaming_response``."""
-
- def __init__(self, resource: AsyncExperimentalResource) -> None:
- self._resource = resource
-
- @cached_property
- def persistent_pty(self) -> AsyncExperimentalPersistentPtyResourceWithStreamingResponse:
- """``client.experimental.persistent_pty.with_streaming_response``."""
- return AsyncExperimentalPersistentPtyResourceWithStreamingResponse(self._resource.persistent_pty)
diff --git a/src/opencode/resources/integration.py b/src/opencode/resources/integration.py
deleted file mode 100644
index dee1c27..0000000
--- a/src/opencode/resources/integration.py
+++ /dev/null
@@ -1,1443 +0,0 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``integration`` resource."""
-
-from __future__ import annotations
-
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
-
-from .._base_client import RequestOptions
-from .._qs import encode_path_segment
-from .._resource import AsyncAPIResource, SyncAPIResource
-from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
-)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- FormAnswer,
- IntegrationAttemptID,
- IntegrationCommandConnectResponse,
- IntegrationCommandStatusResponse,
- IntegrationGetResponse,
- IntegrationID,
- IntegrationListResponse,
- IntegrationMethodID,
- IntegrationOauthConnectResponse,
- IntegrationOauthStatusResponse,
- Location,
- )
-
-__all__ = ["IntegrationResource", "AsyncIntegrationResource"]
-
-
-class IntegrationWellknownResource(SyncAPIResource):
- """Operations under ``client.integration.wellknown`` (OpenCode ``integration.wellknown``)."""
-
- @cached_property
- def with_raw_response(self) -> IntegrationWellknownResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return IntegrationWellknownResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> IntegrationWellknownResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return IntegrationWellknownResourceWithStreamingResponse(self)
-
- def add(
- self,
- *,
- url: str,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Add wellknown integration.
-
- Discover and persist an experimental wellknown integration source.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/experimental/integration/wellknown``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/experimental/integration/wellknown",
- query={"location": self._location(location)},
- body={"url": url},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class IntegrationWellknownResourceWithRawResponse:
- """``client.integration.wellknown.with_raw_response``."""
-
- def __init__(self, resource: IntegrationWellknownResource) -> None:
- self._resource = resource
- self.add = to_raw_response_wrapper(resource.add)
-
-
-class IntegrationWellknownResourceWithStreamingResponse:
- """``client.integration.wellknown.with_streaming_response``."""
-
- def __init__(self, resource: IntegrationWellknownResource) -> None:
- self._resource = resource
- self.add = to_streamed_response_wrapper(resource.add)
-
-
-class IntegrationConnectResource(SyncAPIResource):
- """Operations under ``client.integration.connect`` (OpenCode ``integration.connect``)."""
-
- @cached_property
- def with_raw_response(self) -> IntegrationConnectResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return IntegrationConnectResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> IntegrationConnectResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return IntegrationConnectResourceWithStreamingResponse(self)
-
- def key(
- self,
- integration_id: IntegrationID,
- *,
- key: str,
- location: Location | NotGiven | None = NOT_GIVEN,
- answer: FormAnswer | NotGiven | None = NOT_GIVEN,
- label: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Connect with key.
-
- Run a key authentication method and store the resulting credential.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- IntegrationNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/integration/:integrationID/connect/key``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/integration/{encode_path_segment(integration_id)}/connect/key",
- query={"location": self._location(location)},
- body={"key": key, "answer": answer, "label": label},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def external(
- self,
- integration_id: IntegrationID,
- *,
- method_id: IntegrationMethodID,
- location: Location | NotGiven | None = NOT_GIVEN,
- answer: FormAnswer | NotGiven | None = NOT_GIVEN,
- label: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Connect with external credentials.
-
- Run an external authentication method and store a reference to its credential source.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- IntegrationNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/integration/:integrationID/connect/external``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/integration/{encode_path_segment(integration_id)}/connect/external",
- query={"location": self._location(location)},
- body={"methodID": method_id, "answer": answer, "label": label},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class IntegrationConnectResourceWithRawResponse:
- """``client.integration.connect.with_raw_response``."""
-
- def __init__(self, resource: IntegrationConnectResource) -> None:
- self._resource = resource
- self.key = to_raw_response_wrapper(resource.key)
- self.external = to_raw_response_wrapper(resource.external)
-
-
-class IntegrationConnectResourceWithStreamingResponse:
- """``client.integration.connect.with_streaming_response``."""
-
- def __init__(self, resource: IntegrationConnectResource) -> None:
- self._resource = resource
- self.key = to_streamed_response_wrapper(resource.key)
- self.external = to_streamed_response_wrapper(resource.external)
-
-
-class IntegrationOauthResource(SyncAPIResource):
- """Operations under ``client.integration.oauth`` (OpenCode ``integration.oauth``)."""
-
- @cached_property
- def with_raw_response(self) -> IntegrationOauthResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return IntegrationOauthResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> IntegrationOauthResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return IntegrationOauthResourceWithStreamingResponse(self)
-
- def connect(
- self,
- integration_id: IntegrationID,
- *,
- method_id: IntegrationMethodID,
- location: Location | NotGiven | None = NOT_GIVEN,
- answer: FormAnswer | NotGiven | None = NOT_GIVEN,
- label: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> IntegrationOauthConnectResponse:
- """Begin OAuth connection.
-
- Start an OAuth attempt and return the authorization details.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/integration/:integrationID/connect/oauth``
- """
- return cast(
- "IntegrationOauthConnectResponse",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/integration/{encode_path_segment(integration_id)}/connect/oauth",
- query={"location": self._location(location)},
- body={"methodID": method_id, "answer": answer, "label": label},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="IntegrationOauthConnectResponse",
- ),
- )
-
- def status(
- self,
- integration_id: IntegrationID,
- attempt_id: IntegrationAttemptID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> IntegrationOauthStatusResponse:
- """Get OAuth attempt status.
-
- Poll the current status of an OAuth attempt.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- IntegrationNotFoundError: The server answered 404.
- IntegrationAttemptNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/integration/:integrationID/connect/oauth/:attemptID``
- """
- return cast(
- "IntegrationOauthStatusResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/integration/{encode_path_segment(integration_id)}/connect/oauth/{encode_path_segment(attempt_id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="IntegrationOauthStatusResponse",
- ),
- )
-
- def complete(
- self,
- integration_id: IntegrationID,
- attempt_id: IntegrationAttemptID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- code: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Complete OAuth connection.
-
- Complete a code-based OAuth attempt and store the resulting credential.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- IntegrationNotFoundError: The server answered 404.
- IntegrationAttemptNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/integration/:integrationID/connect/oauth/:attemptID/complete``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/integration/{encode_path_segment(integration_id)}/connect/oauth/{encode_path_segment(attempt_id)}/complete",
- query={"location": self._location(location)},
- body={"code": code},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def cancel(
- self,
- integration_id: IntegrationID,
- attempt_id: IntegrationAttemptID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Cancel OAuth connection.
-
- Cancel an OAuth attempt and release its resources.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``DELETE /api/integration/:integrationID/connect/oauth/:attemptID``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/integration/{encode_path_segment(integration_id)}/connect/oauth/{encode_path_segment(attempt_id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class IntegrationOauthResourceWithRawResponse:
- """``client.integration.oauth.with_raw_response``."""
-
- def __init__(self, resource: IntegrationOauthResource) -> None:
- self._resource = resource
- self.connect = to_raw_response_wrapper(resource.connect)
- self.status = to_raw_response_wrapper(resource.status)
- self.complete = to_raw_response_wrapper(resource.complete)
- self.cancel = to_raw_response_wrapper(resource.cancel)
-
-
-class IntegrationOauthResourceWithStreamingResponse:
- """``client.integration.oauth.with_streaming_response``."""
-
- def __init__(self, resource: IntegrationOauthResource) -> None:
- self._resource = resource
- self.connect = to_streamed_response_wrapper(resource.connect)
- self.status = to_streamed_response_wrapper(resource.status)
- self.complete = to_streamed_response_wrapper(resource.complete)
- self.cancel = to_streamed_response_wrapper(resource.cancel)
-
-
-class IntegrationCommandResource(SyncAPIResource):
- """Operations under ``client.integration.command`` (OpenCode ``integration.command``)."""
-
- @cached_property
- def with_raw_response(self) -> IntegrationCommandResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return IntegrationCommandResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> IntegrationCommandResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return IntegrationCommandResourceWithStreamingResponse(self)
-
- def connect(
- self,
- integration_id: IntegrationID,
- *,
- method_id: IntegrationMethodID,
- location: Location | NotGiven | None = NOT_GIVEN,
- label: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> IntegrationCommandConnectResponse:
- """Begin command connection.
-
- Start a command authentication attempt.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- IntegrationNotFoundError: The server answered 404.
- IntegrationMethodNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/integration/:integrationID/connect/command``
- """
- return cast(
- "IntegrationCommandConnectResponse",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/integration/{encode_path_segment(integration_id)}/connect/command",
- query={"location": self._location(location)},
- body={"methodID": method_id, "label": label},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="IntegrationCommandConnectResponse",
- ),
- )
-
- def status(
- self,
- integration_id: IntegrationID,
- attempt_id: IntegrationAttemptID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> IntegrationCommandStatusResponse:
- """Get command attempt status.
-
- Poll the current status and output of a command authentication attempt.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- IntegrationNotFoundError: The server answered 404.
- IntegrationAttemptNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/integration/:integrationID/connect/command/:attemptID``
- """
- return cast(
- "IntegrationCommandStatusResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/integration/{encode_path_segment(integration_id)}/connect/command/{encode_path_segment(attempt_id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="IntegrationCommandStatusResponse",
- ),
- )
-
- def cancel(
- self,
- integration_id: IntegrationID,
- attempt_id: IntegrationAttemptID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Cancel command connection.
-
- Cancel a command authentication attempt and terminate its process.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``DELETE /api/integration/:integrationID/connect/command/:attemptID``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/integration/{encode_path_segment(integration_id)}/connect/command/{encode_path_segment(attempt_id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class IntegrationCommandResourceWithRawResponse:
- """``client.integration.command.with_raw_response``."""
-
- def __init__(self, resource: IntegrationCommandResource) -> None:
- self._resource = resource
- self.connect = to_raw_response_wrapper(resource.connect)
- self.status = to_raw_response_wrapper(resource.status)
- self.cancel = to_raw_response_wrapper(resource.cancel)
-
-
-class IntegrationCommandResourceWithStreamingResponse:
- """``client.integration.command.with_streaming_response``."""
-
- def __init__(self, resource: IntegrationCommandResource) -> None:
- self._resource = resource
- self.connect = to_streamed_response_wrapper(resource.connect)
- self.status = to_streamed_response_wrapper(resource.status)
- self.cancel = to_streamed_response_wrapper(resource.cancel)
-
-
-class IntegrationResource(SyncAPIResource):
- """Operations under ``client.integration`` (OpenCode ``integration``)."""
-
- @cached_property
- def wellknown(self) -> IntegrationWellknownResource:
- """Operations under ``client.integration.wellknown``."""
- return IntegrationWellknownResource(self._client)
-
- @cached_property
- def connect(self) -> IntegrationConnectResource:
- """Operations under ``client.integration.connect``."""
- return IntegrationConnectResource(self._client)
-
- @cached_property
- def oauth(self) -> IntegrationOauthResource:
- """Operations under ``client.integration.oauth``."""
- return IntegrationOauthResource(self._client)
-
- @cached_property
- def command(self) -> IntegrationCommandResource:
- """Operations under ``client.integration.command``."""
- return IntegrationCommandResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> IntegrationResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return IntegrationResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> IntegrationResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return IntegrationResourceWithStreamingResponse(self)
-
- def list(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> IntegrationListResponse:
- """List integrations.
-
- Retrieve available integrations and their authentication methods.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/integration``
- """
- return cast(
- "IntegrationListResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/integration",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="IntegrationListResponse",
- ),
- )
-
- def get(
- self,
- integration_id: IntegrationID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> IntegrationGetResponse:
- """Get integration.
-
- Retrieve one integration and its authentication methods.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- IntegrationNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/integration/:integrationID``
- """
- return cast(
- "IntegrationGetResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/integration/{encode_path_segment(integration_id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="IntegrationGetResponse",
- ),
- )
-
-
-class IntegrationResourceWithRawResponse:
- """``client.integration.with_raw_response``."""
-
- def __init__(self, resource: IntegrationResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
- self.get = to_raw_response_wrapper(resource.get)
-
- @cached_property
- def wellknown(self) -> IntegrationWellknownResourceWithRawResponse:
- """``client.integration.wellknown.with_raw_response``."""
- return IntegrationWellknownResourceWithRawResponse(self._resource.wellknown)
-
- @cached_property
- def connect(self) -> IntegrationConnectResourceWithRawResponse:
- """``client.integration.connect.with_raw_response``."""
- return IntegrationConnectResourceWithRawResponse(self._resource.connect)
-
- @cached_property
- def oauth(self) -> IntegrationOauthResourceWithRawResponse:
- """``client.integration.oauth.with_raw_response``."""
- return IntegrationOauthResourceWithRawResponse(self._resource.oauth)
-
- @cached_property
- def command(self) -> IntegrationCommandResourceWithRawResponse:
- """``client.integration.command.with_raw_response``."""
- return IntegrationCommandResourceWithRawResponse(self._resource.command)
-
-
-class IntegrationResourceWithStreamingResponse:
- """``client.integration.with_streaming_response``."""
-
- def __init__(self, resource: IntegrationResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
- self.get = to_streamed_response_wrapper(resource.get)
-
- @cached_property
- def wellknown(self) -> IntegrationWellknownResourceWithStreamingResponse:
- """``client.integration.wellknown.with_streaming_response``."""
- return IntegrationWellknownResourceWithStreamingResponse(self._resource.wellknown)
-
- @cached_property
- def connect(self) -> IntegrationConnectResourceWithStreamingResponse:
- """``client.integration.connect.with_streaming_response``."""
- return IntegrationConnectResourceWithStreamingResponse(self._resource.connect)
-
- @cached_property
- def oauth(self) -> IntegrationOauthResourceWithStreamingResponse:
- """``client.integration.oauth.with_streaming_response``."""
- return IntegrationOauthResourceWithStreamingResponse(self._resource.oauth)
-
- @cached_property
- def command(self) -> IntegrationCommandResourceWithStreamingResponse:
- """``client.integration.command.with_streaming_response``."""
- return IntegrationCommandResourceWithStreamingResponse(self._resource.command)
-
-
-class AsyncIntegrationWellknownResource(AsyncAPIResource):
- """Operations under ``client.integration.wellknown`` (OpenCode ``integration.wellknown``)."""
-
- @cached_property
- def with_raw_response(self) -> AsyncIntegrationWellknownResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncIntegrationWellknownResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncIntegrationWellknownResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncIntegrationWellknownResourceWithStreamingResponse(self)
-
- async def add(
- self,
- *,
- url: str,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Add wellknown integration.
-
- Discover and persist an experimental wellknown integration source.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/experimental/integration/wellknown``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/experimental/integration/wellknown",
- query={"location": self._location(location)},
- body={"url": url},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class AsyncIntegrationWellknownResourceWithRawResponse:
- """``client.integration.wellknown.with_raw_response``."""
-
- def __init__(self, resource: AsyncIntegrationWellknownResource) -> None:
- self._resource = resource
- self.add = async_to_raw_response_wrapper(resource.add)
-
-
-class AsyncIntegrationWellknownResourceWithStreamingResponse:
- """``client.integration.wellknown.with_streaming_response``."""
-
- def __init__(self, resource: AsyncIntegrationWellknownResource) -> None:
- self._resource = resource
- self.add = async_to_streamed_response_wrapper(resource.add)
-
-
-class AsyncIntegrationConnectResource(AsyncAPIResource):
- """Operations under ``client.integration.connect`` (OpenCode ``integration.connect``)."""
-
- @cached_property
- def with_raw_response(self) -> AsyncIntegrationConnectResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncIntegrationConnectResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncIntegrationConnectResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncIntegrationConnectResourceWithStreamingResponse(self)
-
- async def key(
- self,
- integration_id: IntegrationID,
- *,
- key: str,
- location: Location | NotGiven | None = NOT_GIVEN,
- answer: FormAnswer | NotGiven | None = NOT_GIVEN,
- label: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Connect with key.
-
- Run a key authentication method and store the resulting credential.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- IntegrationNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/integration/:integrationID/connect/key``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/integration/{encode_path_segment(integration_id)}/connect/key",
- query={"location": self._location(location)},
- body={"key": key, "answer": answer, "label": label},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def external(
- self,
- integration_id: IntegrationID,
- *,
- method_id: IntegrationMethodID,
- location: Location | NotGiven | None = NOT_GIVEN,
- answer: FormAnswer | NotGiven | None = NOT_GIVEN,
- label: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Connect with external credentials.
-
- Run an external authentication method and store a reference to its credential source.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- IntegrationNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/integration/:integrationID/connect/external``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/integration/{encode_path_segment(integration_id)}/connect/external",
- query={"location": self._location(location)},
- body={"methodID": method_id, "answer": answer, "label": label},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class AsyncIntegrationConnectResourceWithRawResponse:
- """``client.integration.connect.with_raw_response``."""
-
- def __init__(self, resource: AsyncIntegrationConnectResource) -> None:
- self._resource = resource
- self.key = async_to_raw_response_wrapper(resource.key)
- self.external = async_to_raw_response_wrapper(resource.external)
-
-
-class AsyncIntegrationConnectResourceWithStreamingResponse:
- """``client.integration.connect.with_streaming_response``."""
-
- def __init__(self, resource: AsyncIntegrationConnectResource) -> None:
- self._resource = resource
- self.key = async_to_streamed_response_wrapper(resource.key)
- self.external = async_to_streamed_response_wrapper(resource.external)
-
-
-class AsyncIntegrationOauthResource(AsyncAPIResource):
- """Operations under ``client.integration.oauth`` (OpenCode ``integration.oauth``)."""
-
- @cached_property
- def with_raw_response(self) -> AsyncIntegrationOauthResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncIntegrationOauthResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncIntegrationOauthResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncIntegrationOauthResourceWithStreamingResponse(self)
-
- async def connect(
- self,
- integration_id: IntegrationID,
- *,
- method_id: IntegrationMethodID,
- location: Location | NotGiven | None = NOT_GIVEN,
- answer: FormAnswer | NotGiven | None = NOT_GIVEN,
- label: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> IntegrationOauthConnectResponse:
- """Begin OAuth connection.
-
- Start an OAuth attempt and return the authorization details.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/integration/:integrationID/connect/oauth``
- """
- return cast(
- "IntegrationOauthConnectResponse",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/integration/{encode_path_segment(integration_id)}/connect/oauth",
- query={"location": self._location(location)},
- body={"methodID": method_id, "answer": answer, "label": label},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="IntegrationOauthConnectResponse",
- ),
- )
-
- async def status(
- self,
- integration_id: IntegrationID,
- attempt_id: IntegrationAttemptID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> IntegrationOauthStatusResponse:
- """Get OAuth attempt status.
-
- Poll the current status of an OAuth attempt.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- IntegrationNotFoundError: The server answered 404.
- IntegrationAttemptNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/integration/:integrationID/connect/oauth/:attemptID``
- """
- return cast(
- "IntegrationOauthStatusResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/integration/{encode_path_segment(integration_id)}/connect/oauth/{encode_path_segment(attempt_id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="IntegrationOauthStatusResponse",
- ),
- )
-
- async def complete(
- self,
- integration_id: IntegrationID,
- attempt_id: IntegrationAttemptID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- code: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Complete OAuth connection.
-
- Complete a code-based OAuth attempt and store the resulting credential.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- IntegrationNotFoundError: The server answered 404.
- IntegrationAttemptNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/integration/:integrationID/connect/oauth/:attemptID/complete``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/integration/{encode_path_segment(integration_id)}/connect/oauth/{encode_path_segment(attempt_id)}/complete",
- query={"location": self._location(location)},
- body={"code": code},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def cancel(
- self,
- integration_id: IntegrationID,
- attempt_id: IntegrationAttemptID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Cancel OAuth connection.
-
- Cancel an OAuth attempt and release its resources.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``DELETE /api/integration/:integrationID/connect/oauth/:attemptID``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/integration/{encode_path_segment(integration_id)}/connect/oauth/{encode_path_segment(attempt_id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class AsyncIntegrationOauthResourceWithRawResponse:
- """``client.integration.oauth.with_raw_response``."""
-
- def __init__(self, resource: AsyncIntegrationOauthResource) -> None:
- self._resource = resource
- self.connect = async_to_raw_response_wrapper(resource.connect)
- self.status = async_to_raw_response_wrapper(resource.status)
- self.complete = async_to_raw_response_wrapper(resource.complete)
- self.cancel = async_to_raw_response_wrapper(resource.cancel)
-
-
-class AsyncIntegrationOauthResourceWithStreamingResponse:
- """``client.integration.oauth.with_streaming_response``."""
-
- def __init__(self, resource: AsyncIntegrationOauthResource) -> None:
- self._resource = resource
- self.connect = async_to_streamed_response_wrapper(resource.connect)
- self.status = async_to_streamed_response_wrapper(resource.status)
- self.complete = async_to_streamed_response_wrapper(resource.complete)
- self.cancel = async_to_streamed_response_wrapper(resource.cancel)
-
-
-class AsyncIntegrationCommandResource(AsyncAPIResource):
- """Operations under ``client.integration.command`` (OpenCode ``integration.command``)."""
-
- @cached_property
- def with_raw_response(self) -> AsyncIntegrationCommandResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncIntegrationCommandResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncIntegrationCommandResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncIntegrationCommandResourceWithStreamingResponse(self)
-
- async def connect(
- self,
- integration_id: IntegrationID,
- *,
- method_id: IntegrationMethodID,
- location: Location | NotGiven | None = NOT_GIVEN,
- label: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> IntegrationCommandConnectResponse:
- """Begin command connection.
-
- Start a command authentication attempt.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- IntegrationNotFoundError: The server answered 404.
- IntegrationMethodNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/integration/:integrationID/connect/command``
- """
- return cast(
- "IntegrationCommandConnectResponse",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/integration/{encode_path_segment(integration_id)}/connect/command",
- query={"location": self._location(location)},
- body={"methodID": method_id, "label": label},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="IntegrationCommandConnectResponse",
- ),
- )
-
- async def status(
- self,
- integration_id: IntegrationID,
- attempt_id: IntegrationAttemptID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> IntegrationCommandStatusResponse:
- """Get command attempt status.
-
- Poll the current status and output of a command authentication attempt.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- IntegrationNotFoundError: The server answered 404.
- IntegrationAttemptNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/integration/:integrationID/connect/command/:attemptID``
- """
- return cast(
- "IntegrationCommandStatusResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/integration/{encode_path_segment(integration_id)}/connect/command/{encode_path_segment(attempt_id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="IntegrationCommandStatusResponse",
- ),
- )
-
- async def cancel(
- self,
- integration_id: IntegrationID,
- attempt_id: IntegrationAttemptID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Cancel command connection.
-
- Cancel a command authentication attempt and terminate its process.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``DELETE /api/integration/:integrationID/connect/command/:attemptID``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/integration/{encode_path_segment(integration_id)}/connect/command/{encode_path_segment(attempt_id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class AsyncIntegrationCommandResourceWithRawResponse:
- """``client.integration.command.with_raw_response``."""
-
- def __init__(self, resource: AsyncIntegrationCommandResource) -> None:
- self._resource = resource
- self.connect = async_to_raw_response_wrapper(resource.connect)
- self.status = async_to_raw_response_wrapper(resource.status)
- self.cancel = async_to_raw_response_wrapper(resource.cancel)
-
-
-class AsyncIntegrationCommandResourceWithStreamingResponse:
- """``client.integration.command.with_streaming_response``."""
-
- def __init__(self, resource: AsyncIntegrationCommandResource) -> None:
- self._resource = resource
- self.connect = async_to_streamed_response_wrapper(resource.connect)
- self.status = async_to_streamed_response_wrapper(resource.status)
- self.cancel = async_to_streamed_response_wrapper(resource.cancel)
-
-
-class AsyncIntegrationResource(AsyncAPIResource):
- """Operations under ``client.integration`` (OpenCode ``integration``)."""
-
- @cached_property
- def wellknown(self) -> AsyncIntegrationWellknownResource:
- """Operations under ``client.integration.wellknown``."""
- return AsyncIntegrationWellknownResource(self._client)
-
- @cached_property
- def connect(self) -> AsyncIntegrationConnectResource:
- """Operations under ``client.integration.connect``."""
- return AsyncIntegrationConnectResource(self._client)
-
- @cached_property
- def oauth(self) -> AsyncIntegrationOauthResource:
- """Operations under ``client.integration.oauth``."""
- return AsyncIntegrationOauthResource(self._client)
-
- @cached_property
- def command(self) -> AsyncIntegrationCommandResource:
- """Operations under ``client.integration.command``."""
- return AsyncIntegrationCommandResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> AsyncIntegrationResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncIntegrationResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncIntegrationResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncIntegrationResourceWithStreamingResponse(self)
-
- async def list(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> IntegrationListResponse:
- """List integrations.
-
- Retrieve available integrations and their authentication methods.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/integration``
- """
- return cast(
- "IntegrationListResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/integration",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="IntegrationListResponse",
- ),
- )
-
- async def get(
- self,
- integration_id: IntegrationID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> IntegrationGetResponse:
- """Get integration.
-
- Retrieve one integration and its authentication methods.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- IntegrationNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/integration/:integrationID``
- """
- return cast(
- "IntegrationGetResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/integration/{encode_path_segment(integration_id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="IntegrationGetResponse",
- ),
- )
-
-
-class AsyncIntegrationResourceWithRawResponse:
- """``client.integration.with_raw_response``."""
-
- def __init__(self, resource: AsyncIntegrationResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
- self.get = async_to_raw_response_wrapper(resource.get)
-
- @cached_property
- def wellknown(self) -> AsyncIntegrationWellknownResourceWithRawResponse:
- """``client.integration.wellknown.with_raw_response``."""
- return AsyncIntegrationWellknownResourceWithRawResponse(self._resource.wellknown)
-
- @cached_property
- def connect(self) -> AsyncIntegrationConnectResourceWithRawResponse:
- """``client.integration.connect.with_raw_response``."""
- return AsyncIntegrationConnectResourceWithRawResponse(self._resource.connect)
-
- @cached_property
- def oauth(self) -> AsyncIntegrationOauthResourceWithRawResponse:
- """``client.integration.oauth.with_raw_response``."""
- return AsyncIntegrationOauthResourceWithRawResponse(self._resource.oauth)
-
- @cached_property
- def command(self) -> AsyncIntegrationCommandResourceWithRawResponse:
- """``client.integration.command.with_raw_response``."""
- return AsyncIntegrationCommandResourceWithRawResponse(self._resource.command)
-
-
-class AsyncIntegrationResourceWithStreamingResponse:
- """``client.integration.with_streaming_response``."""
-
- def __init__(self, resource: AsyncIntegrationResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.get = async_to_streamed_response_wrapper(resource.get)
-
- @cached_property
- def wellknown(self) -> AsyncIntegrationWellknownResourceWithStreamingResponse:
- """``client.integration.wellknown.with_streaming_response``."""
- return AsyncIntegrationWellknownResourceWithStreamingResponse(self._resource.wellknown)
-
- @cached_property
- def connect(self) -> AsyncIntegrationConnectResourceWithStreamingResponse:
- """``client.integration.connect.with_streaming_response``."""
- return AsyncIntegrationConnectResourceWithStreamingResponse(self._resource.connect)
-
- @cached_property
- def oauth(self) -> AsyncIntegrationOauthResourceWithStreamingResponse:
- """``client.integration.oauth.with_streaming_response``."""
- return AsyncIntegrationOauthResourceWithStreamingResponse(self._resource.oauth)
-
- @cached_property
- def command(self) -> AsyncIntegrationCommandResourceWithStreamingResponse:
- """``client.integration.command.with_streaming_response``."""
- return AsyncIntegrationCommandResourceWithStreamingResponse(self._resource.command)
diff --git a/src/opencode/resources/mcp.py b/src/opencode/resources/mcp.py
deleted file mode 100644
index 4f60a15..0000000
--- a/src/opencode/resources/mcp.py
+++ /dev/null
@@ -1,662 +0,0 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``mcp`` resource."""
-
-from __future__ import annotations
-
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
-
-from .._base_client import RequestOptions
-from .._qs import encode_path_segment
-from .._resource import AsyncAPIResource, SyncAPIResource
-from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
-)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- Location,
- McpAddConfig,
- McpListResponse,
- McpResourceCatalogResponse,
- )
-
-__all__ = ["McpResource", "AsyncMcpResource"]
-
-
-class McpResourceResource(SyncAPIResource):
- """Operations under ``client.mcp.resource`` (OpenCode ``mcp.resource``)."""
-
- @cached_property
- def with_raw_response(self) -> McpResourceResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return McpResourceResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> McpResourceResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return McpResourceResourceWithStreamingResponse(self)
-
- def catalog(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> McpResourceCatalogResponse:
- """List MCP resources.
-
- Retrieve resources and resource templates from connected MCP servers.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/mcp/resource``
- """
- return cast(
- "McpResourceCatalogResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/mcp/resource",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="McpResourceCatalogResponse",
- ),
- )
-
-
-class McpResourceResourceWithRawResponse:
- """``client.mcp.resource.with_raw_response``."""
-
- def __init__(self, resource: McpResourceResource) -> None:
- self._resource = resource
- self.catalog = to_raw_response_wrapper(resource.catalog)
-
-
-class McpResourceResourceWithStreamingResponse:
- """``client.mcp.resource.with_streaming_response``."""
-
- def __init__(self, resource: McpResourceResource) -> None:
- self._resource = resource
- self.catalog = to_streamed_response_wrapper(resource.catalog)
-
-
-class McpResource(SyncAPIResource):
- """Operations under ``client.mcp`` (OpenCode ``mcp``)."""
-
- @cached_property
- def resource(self) -> McpResourceResource:
- """Operations under ``client.mcp.resource``."""
- return McpResourceResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> McpResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return McpResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> McpResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return McpResourceWithStreamingResponse(self)
-
- def list(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> McpListResponse:
- """List MCP servers.
-
- Retrieve configured MCP servers and their connection status.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/mcp``
- """
- return cast(
- "McpListResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/mcp",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="McpListResponse",
- ),
- )
-
- def add(
- self,
- server: str,
- *,
- config: McpAddConfig,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Add MCP server.
-
- Add an MCP server at runtime or replace an existing one, connecting it immediately.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``PUT /api/experimental/mcp/:server``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="PUT",
- path=f"/api/experimental/mcp/{encode_path_segment(server)}",
- query={"location": self._location(location)},
- body={"config": config},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def remove(
- self,
- server: str,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Remove MCP server.
-
- Stop an MCP server and remove it from the runtime set until restart.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- McpServerNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``DELETE /api/experimental/mcp/:server``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/experimental/mcp/{encode_path_segment(server)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def connect(
- self,
- server: str,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Connect MCP server.
-
- Connect an MCP server at runtime, overriding a disabled configuration until restart.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- McpServerNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/experimental/mcp/:server/connect``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/experimental/mcp/{encode_path_segment(server)}/connect",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def disconnect(
- self,
- server: str,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Disconnect MCP server.
-
- Disconnect an MCP server at runtime, removing its tools until reconnected.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- McpServerNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/experimental/mcp/:server/disconnect``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/experimental/mcp/{encode_path_segment(server)}/disconnect",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class McpResourceWithRawResponse:
- """``client.mcp.with_raw_response``."""
-
- def __init__(self, resource: McpResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
- self.add = to_raw_response_wrapper(resource.add)
- self.remove = to_raw_response_wrapper(resource.remove)
- self.connect = to_raw_response_wrapper(resource.connect)
- self.disconnect = to_raw_response_wrapper(resource.disconnect)
-
- @cached_property
- def resource(self) -> McpResourceResourceWithRawResponse:
- """``client.mcp.resource.with_raw_response``."""
- return McpResourceResourceWithRawResponse(self._resource.resource)
-
-
-class McpResourceWithStreamingResponse:
- """``client.mcp.with_streaming_response``."""
-
- def __init__(self, resource: McpResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
- self.add = to_streamed_response_wrapper(resource.add)
- self.remove = to_streamed_response_wrapper(resource.remove)
- self.connect = to_streamed_response_wrapper(resource.connect)
- self.disconnect = to_streamed_response_wrapper(resource.disconnect)
-
- @cached_property
- def resource(self) -> McpResourceResourceWithStreamingResponse:
- """``client.mcp.resource.with_streaming_response``."""
- return McpResourceResourceWithStreamingResponse(self._resource.resource)
-
-
-class AsyncMcpResourceResource(AsyncAPIResource):
- """Operations under ``client.mcp.resource`` (OpenCode ``mcp.resource``)."""
-
- @cached_property
- def with_raw_response(self) -> AsyncMcpResourceResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncMcpResourceResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncMcpResourceResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncMcpResourceResourceWithStreamingResponse(self)
-
- async def catalog(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> McpResourceCatalogResponse:
- """List MCP resources.
-
- Retrieve resources and resource templates from connected MCP servers.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/mcp/resource``
- """
- return cast(
- "McpResourceCatalogResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/mcp/resource",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="McpResourceCatalogResponse",
- ),
- )
-
-
-class AsyncMcpResourceResourceWithRawResponse:
- """``client.mcp.resource.with_raw_response``."""
-
- def __init__(self, resource: AsyncMcpResourceResource) -> None:
- self._resource = resource
- self.catalog = async_to_raw_response_wrapper(resource.catalog)
-
-
-class AsyncMcpResourceResourceWithStreamingResponse:
- """``client.mcp.resource.with_streaming_response``."""
-
- def __init__(self, resource: AsyncMcpResourceResource) -> None:
- self._resource = resource
- self.catalog = async_to_streamed_response_wrapper(resource.catalog)
-
-
-class AsyncMcpResource(AsyncAPIResource):
- """Operations under ``client.mcp`` (OpenCode ``mcp``)."""
-
- @cached_property
- def resource(self) -> AsyncMcpResourceResource:
- """Operations under ``client.mcp.resource``."""
- return AsyncMcpResourceResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> AsyncMcpResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncMcpResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncMcpResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncMcpResourceWithStreamingResponse(self)
-
- async def list(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> McpListResponse:
- """List MCP servers.
-
- Retrieve configured MCP servers and their connection status.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/mcp``
- """
- return cast(
- "McpListResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/mcp",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="McpListResponse",
- ),
- )
-
- async def add(
- self,
- server: str,
- *,
- config: McpAddConfig,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Add MCP server.
-
- Add an MCP server at runtime or replace an existing one, connecting it immediately.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``PUT /api/experimental/mcp/:server``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="PUT",
- path=f"/api/experimental/mcp/{encode_path_segment(server)}",
- query={"location": self._location(location)},
- body={"config": config},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def remove(
- self,
- server: str,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Remove MCP server.
-
- Stop an MCP server and remove it from the runtime set until restart.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- McpServerNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``DELETE /api/experimental/mcp/:server``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/experimental/mcp/{encode_path_segment(server)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def connect(
- self,
- server: str,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Connect MCP server.
-
- Connect an MCP server at runtime, overriding a disabled configuration until restart.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- McpServerNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/experimental/mcp/:server/connect``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/experimental/mcp/{encode_path_segment(server)}/connect",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def disconnect(
- self,
- server: str,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Disconnect MCP server.
-
- Disconnect an MCP server at runtime, removing its tools until reconnected.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- McpServerNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/experimental/mcp/:server/disconnect``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/experimental/mcp/{encode_path_segment(server)}/disconnect",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class AsyncMcpResourceWithRawResponse:
- """``client.mcp.with_raw_response``."""
-
- def __init__(self, resource: AsyncMcpResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
- self.add = async_to_raw_response_wrapper(resource.add)
- self.remove = async_to_raw_response_wrapper(resource.remove)
- self.connect = async_to_raw_response_wrapper(resource.connect)
- self.disconnect = async_to_raw_response_wrapper(resource.disconnect)
-
- @cached_property
- def resource(self) -> AsyncMcpResourceResourceWithRawResponse:
- """``client.mcp.resource.with_raw_response``."""
- return AsyncMcpResourceResourceWithRawResponse(self._resource.resource)
-
-
-class AsyncMcpResourceWithStreamingResponse:
- """``client.mcp.with_streaming_response``."""
-
- def __init__(self, resource: AsyncMcpResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.add = async_to_streamed_response_wrapper(resource.add)
- self.remove = async_to_streamed_response_wrapper(resource.remove)
- self.connect = async_to_streamed_response_wrapper(resource.connect)
- self.disconnect = async_to_streamed_response_wrapper(resource.disconnect)
-
- @cached_property
- def resource(self) -> AsyncMcpResourceResourceWithStreamingResponse:
- """``client.mcp.resource.with_streaming_response``."""
- return AsyncMcpResourceResourceWithStreamingResponse(self._resource.resource)
diff --git a/src/opencode/resources/migration.py b/src/opencode/resources/migration.py
deleted file mode 100644
index 5fb8f43..0000000
--- a/src/opencode/resources/migration.py
+++ /dev/null
@@ -1,244 +0,0 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``migration`` resource."""
-
-from __future__ import annotations
-
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
-
-from .._base_client import RequestOptions
-from .._resource import AsyncAPIResource, SyncAPIResource
-from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
-)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- MigrationV1StatusResponseOption1,
- MigrationV1StatusResponseOption2,
- MigrationV1StatusResponseOption3,
- )
-
-__all__ = ["MigrationResource", "AsyncMigrationResource"]
-
-
-class MigrationV1Resource(SyncAPIResource):
- """Operations under ``client.migration.v1`` (OpenCode ``migration.v1``)."""
-
- @cached_property
- def with_raw_response(self) -> MigrationV1ResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return MigrationV1ResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> MigrationV1ResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return MigrationV1ResourceWithStreamingResponse(self)
-
- def status(
- self,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> MigrationV1StatusResponseOption1 | MigrationV1StatusResponseOption2 | MigrationV1StatusResponseOption3:
- """Get V1 migration status.
-
- Return the progress of the V1 to V2 session history migration.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
-
- ``GET /api/experimental/migration/v1``
- """
- return cast(
- "MigrationV1StatusResponseOption1 | MigrationV1StatusResponseOption2 | MigrationV1StatusResponseOption3",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/experimental/migration/v1",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="MigrationV1StatusResponseOption1 | MigrationV1StatusResponseOption2 | MigrationV1StatusResponseOption3",
- ),
- )
-
-
-class MigrationV1ResourceWithRawResponse:
- """``client.migration.v1.with_raw_response``."""
-
- def __init__(self, resource: MigrationV1Resource) -> None:
- self._resource = resource
- self.status = to_raw_response_wrapper(resource.status)
-
-
-class MigrationV1ResourceWithStreamingResponse:
- """``client.migration.v1.with_streaming_response``."""
-
- def __init__(self, resource: MigrationV1Resource) -> None:
- self._resource = resource
- self.status = to_streamed_response_wrapper(resource.status)
-
-
-class MigrationResource(SyncAPIResource):
- """Operations under ``client.migration`` (OpenCode ``migration``)."""
-
- @cached_property
- def v1(self) -> MigrationV1Resource:
- """Operations under ``client.migration.v1``."""
- return MigrationV1Resource(self._client)
-
- @cached_property
- def with_raw_response(self) -> MigrationResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return MigrationResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> MigrationResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return MigrationResourceWithStreamingResponse(self)
-
-
-class MigrationResourceWithRawResponse:
- """``client.migration.with_raw_response``."""
-
- def __init__(self, resource: MigrationResource) -> None:
- self._resource = resource
-
- @cached_property
- def v1(self) -> MigrationV1ResourceWithRawResponse:
- """``client.migration.v1.with_raw_response``."""
- return MigrationV1ResourceWithRawResponse(self._resource.v1)
-
-
-class MigrationResourceWithStreamingResponse:
- """``client.migration.with_streaming_response``."""
-
- def __init__(self, resource: MigrationResource) -> None:
- self._resource = resource
-
- @cached_property
- def v1(self) -> MigrationV1ResourceWithStreamingResponse:
- """``client.migration.v1.with_streaming_response``."""
- return MigrationV1ResourceWithStreamingResponse(self._resource.v1)
-
-
-class AsyncMigrationV1Resource(AsyncAPIResource):
- """Operations under ``client.migration.v1`` (OpenCode ``migration.v1``)."""
-
- @cached_property
- def with_raw_response(self) -> AsyncMigrationV1ResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncMigrationV1ResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncMigrationV1ResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncMigrationV1ResourceWithStreamingResponse(self)
-
- async def status(
- self,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> MigrationV1StatusResponseOption1 | MigrationV1StatusResponseOption2 | MigrationV1StatusResponseOption3:
- """Get V1 migration status.
-
- Return the progress of the V1 to V2 session history migration.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
-
- ``GET /api/experimental/migration/v1``
- """
- return cast(
- "MigrationV1StatusResponseOption1 | MigrationV1StatusResponseOption2 | MigrationV1StatusResponseOption3",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/experimental/migration/v1",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="MigrationV1StatusResponseOption1 | MigrationV1StatusResponseOption2 | MigrationV1StatusResponseOption3",
- ),
- )
-
-
-class AsyncMigrationV1ResourceWithRawResponse:
- """``client.migration.v1.with_raw_response``."""
-
- def __init__(self, resource: AsyncMigrationV1Resource) -> None:
- self._resource = resource
- self.status = async_to_raw_response_wrapper(resource.status)
-
-
-class AsyncMigrationV1ResourceWithStreamingResponse:
- """``client.migration.v1.with_streaming_response``."""
-
- def __init__(self, resource: AsyncMigrationV1Resource) -> None:
- self._resource = resource
- self.status = async_to_streamed_response_wrapper(resource.status)
-
-
-class AsyncMigrationResource(AsyncAPIResource):
- """Operations under ``client.migration`` (OpenCode ``migration``)."""
-
- @cached_property
- def v1(self) -> AsyncMigrationV1Resource:
- """Operations under ``client.migration.v1``."""
- return AsyncMigrationV1Resource(self._client)
-
- @cached_property
- def with_raw_response(self) -> AsyncMigrationResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncMigrationResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncMigrationResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncMigrationResourceWithStreamingResponse(self)
-
-
-class AsyncMigrationResourceWithRawResponse:
- """``client.migration.with_raw_response``."""
-
- def __init__(self, resource: AsyncMigrationResource) -> None:
- self._resource = resource
-
- @cached_property
- def v1(self) -> AsyncMigrationV1ResourceWithRawResponse:
- """``client.migration.v1.with_raw_response``."""
- return AsyncMigrationV1ResourceWithRawResponse(self._resource.v1)
-
-
-class AsyncMigrationResourceWithStreamingResponse:
- """``client.migration.with_streaming_response``."""
-
- def __init__(self, resource: AsyncMigrationResource) -> None:
- self._resource = resource
-
- @cached_property
- def v1(self) -> AsyncMigrationV1ResourceWithStreamingResponse:
- """``client.migration.v1.with_streaming_response``."""
- return AsyncMigrationV1ResourceWithStreamingResponse(self._resource.v1)
diff --git a/src/opencode/resources/permission.py b/src/opencode/resources/permission.py
deleted file mode 100644
index df8d1e0..0000000
--- a/src/opencode/resources/permission.py
+++ /dev/null
@@ -1,938 +0,0 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``permission`` resource."""
-
-from __future__ import annotations
-
-import builtins
-from collections.abc import Mapping, Sequence
-from functools import cached_property
-from typing import TYPE_CHECKING, Any, cast
-
-from .._base_client import RequestOptions
-from .._qs import encode_path_segment
-from .._resource import AsyncAPIResource, SyncAPIResource
-from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
-)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- AgentID,
- Location,
- PermissionCreateResponse,
- PermissionID,
- PermissionReply,
- PermissionRequest,
- PermissionRequestListResponse,
- PermissionSavedID,
- PermissionSavedInfo,
- PermissionSource,
- ProjectID,
- SessionID,
- )
-
-__all__ = ["PermissionResource", "AsyncPermissionResource"]
-
-
-class PermissionRequestResource(SyncAPIResource):
- """Operations under ``client.permission.request`` (OpenCode ``permission.request``)."""
-
- @cached_property
- def with_raw_response(self) -> PermissionRequestResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return PermissionRequestResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> PermissionRequestResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return PermissionRequestResourceWithStreamingResponse(self)
-
- def list(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PermissionRequestListResponse:
- """List pending permission requests.
-
- Retrieve pending permission requests for a location.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/permission/request``
- """
- return cast(
- "PermissionRequestListResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/permission/request",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PermissionRequestListResponse",
- ),
- )
-
-
-class PermissionRequestResourceWithRawResponse:
- """``client.permission.request.with_raw_response``."""
-
- def __init__(self, resource: PermissionRequestResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
-
-
-class PermissionRequestResourceWithStreamingResponse:
- """``client.permission.request.with_streaming_response``."""
-
- def __init__(self, resource: PermissionRequestResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
-
-
-class PermissionSavedResource(SyncAPIResource):
- """Operations under ``client.permission.saved`` (OpenCode ``permission.saved``)."""
-
- @cached_property
- def with_raw_response(self) -> PermissionSavedResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return PermissionSavedResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> PermissionSavedResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return PermissionSavedResourceWithStreamingResponse(self)
-
- def list(
- self,
- *,
- project_id: ProjectID | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[PermissionSavedInfo]:
- """List saved permissions.
-
- Retrieve saved permissions, optionally filtered by project.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/permission/saved``
- """
- return cast(
- "builtins.list[PermissionSavedInfo]",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/permission/saved",
- query={"projectID": project_id},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[PermissionSavedInfo]",
- unwrap=True,
- ),
- )
-
- def update(
- self,
- id: PermissionSavedID,
- *,
- action: str | NotGiven | None = NOT_GIVEN,
- resource: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PermissionSavedInfo:
- """Update saved permission.
-
- Change the action or resource of a saved permission. Fails with a conflict when the project already has a saved permission with the same action and resource.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- PermissionSavedNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- ConflictError: The server answered 409.
-
- ``PATCH /api/permission/saved/:id``
- """
- return cast(
- "PermissionSavedInfo",
- self._client.request(
- RequestOptions(
- method="PATCH",
- path=f"/api/permission/saved/{encode_path_segment(id)}",
- body={"action": action, "resource": resource},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PermissionSavedInfo",
- unwrap=True,
- ),
- )
-
- def remove(
- self,
- id: PermissionSavedID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Remove saved permission.
-
- Remove a saved permission by ID.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``DELETE /api/permission/saved/:id``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/permission/saved/{encode_path_segment(id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class PermissionSavedResourceWithRawResponse:
- """``client.permission.saved.with_raw_response``."""
-
- def __init__(self, resource: PermissionSavedResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
- self.update = to_raw_response_wrapper(resource.update)
- self.remove = to_raw_response_wrapper(resource.remove)
-
-
-class PermissionSavedResourceWithStreamingResponse:
- """``client.permission.saved.with_streaming_response``."""
-
- def __init__(self, resource: PermissionSavedResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
- self.update = to_streamed_response_wrapper(resource.update)
- self.remove = to_streamed_response_wrapper(resource.remove)
-
-
-class PermissionResource(SyncAPIResource):
- """Operations under ``client.permission`` (OpenCode ``permission``)."""
-
- @cached_property
- def request_(self) -> PermissionRequestResource:
- """Operations under ``client.permission.request``."""
- return PermissionRequestResource(self._client)
-
- @cached_property
- def saved(self) -> PermissionSavedResource:
- """Operations under ``client.permission.saved``."""
- return PermissionSavedResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> PermissionResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return PermissionResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> PermissionResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return PermissionResourceWithStreamingResponse(self)
-
- def create(
- self,
- session_id: SessionID,
- *,
- action: str,
- resources: Sequence[str],
- id: PermissionID | NotGiven | None = NOT_GIVEN,
- save: Sequence[str] | NotGiven = NOT_GIVEN,
- metadata: Mapping[str, Any] | NotGiven = NOT_GIVEN,
- source: PermissionSource | NotGiven = NOT_GIVEN,
- agent: AgentID | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PermissionCreateResponse:
- """Create permission request.
-
- Evaluate and, when approval is required, create a permission request for a session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/permission``
- """
- return cast(
- "PermissionCreateResponse",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/permission",
- body={
- "id": id,
- "action": action,
- "resources": resources,
- "save": save,
- "metadata": metadata,
- "source": source,
- "agent": agent,
- },
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PermissionCreateResponse",
- unwrap=True,
- ),
- )
-
- def list(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[PermissionRequest]:
- """List session permission requests.
-
- Retrieve pending permission requests owned by a session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/session/:sessionID/permission``
- """
- return cast(
- "builtins.list[PermissionRequest]",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}/permission",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[PermissionRequest]",
- unwrap=True,
- ),
- )
-
- def get(
- self,
- session_id: SessionID,
- request_id: PermissionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PermissionRequest:
- """Get permission request.
-
- Retrieve a pending permission request owned by a session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- PermissionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/session/:sessionID/permission/:requestID``
- """
- return cast(
- "PermissionRequest",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}/permission/{encode_path_segment(request_id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PermissionRequest",
- unwrap=True,
- ),
- )
-
- def reply(
- self,
- session_id: SessionID,
- request_id: PermissionID,
- *,
- decision: PermissionReply,
- message: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Reply to pending permission request.
-
- Respond to a pending permission request owned by a session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- PermissionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/permission/:requestID/reply``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/permission/{encode_path_segment(request_id)}/reply",
- body={"decision": decision, "message": message},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class PermissionResourceWithRawResponse:
- """``client.permission.with_raw_response``."""
-
- def __init__(self, resource: PermissionResource) -> None:
- self._resource = resource
- self.create = to_raw_response_wrapper(resource.create)
- self.list = to_raw_response_wrapper(resource.list)
- self.get = to_raw_response_wrapper(resource.get)
- self.reply = to_raw_response_wrapper(resource.reply)
-
- @cached_property
- def request_(self) -> PermissionRequestResourceWithRawResponse:
- """``client.permission.request.with_raw_response``."""
- return PermissionRequestResourceWithRawResponse(self._resource.request_)
-
- @cached_property
- def saved(self) -> PermissionSavedResourceWithRawResponse:
- """``client.permission.saved.with_raw_response``."""
- return PermissionSavedResourceWithRawResponse(self._resource.saved)
-
-
-class PermissionResourceWithStreamingResponse:
- """``client.permission.with_streaming_response``."""
-
- def __init__(self, resource: PermissionResource) -> None:
- self._resource = resource
- self.create = to_streamed_response_wrapper(resource.create)
- self.list = to_streamed_response_wrapper(resource.list)
- self.get = to_streamed_response_wrapper(resource.get)
- self.reply = to_streamed_response_wrapper(resource.reply)
-
- @cached_property
- def request_(self) -> PermissionRequestResourceWithStreamingResponse:
- """``client.permission.request.with_streaming_response``."""
- return PermissionRequestResourceWithStreamingResponse(self._resource.request_)
-
- @cached_property
- def saved(self) -> PermissionSavedResourceWithStreamingResponse:
- """``client.permission.saved.with_streaming_response``."""
- return PermissionSavedResourceWithStreamingResponse(self._resource.saved)
-
-
-class AsyncPermissionRequestResource(AsyncAPIResource):
- """Operations under ``client.permission.request`` (OpenCode ``permission.request``)."""
-
- @cached_property
- def with_raw_response(self) -> AsyncPermissionRequestResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncPermissionRequestResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncPermissionRequestResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncPermissionRequestResourceWithStreamingResponse(self)
-
- async def list(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PermissionRequestListResponse:
- """List pending permission requests.
-
- Retrieve pending permission requests for a location.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/permission/request``
- """
- return cast(
- "PermissionRequestListResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/permission/request",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PermissionRequestListResponse",
- ),
- )
-
-
-class AsyncPermissionRequestResourceWithRawResponse:
- """``client.permission.request.with_raw_response``."""
-
- def __init__(self, resource: AsyncPermissionRequestResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
-
-
-class AsyncPermissionRequestResourceWithStreamingResponse:
- """``client.permission.request.with_streaming_response``."""
-
- def __init__(self, resource: AsyncPermissionRequestResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
-
-
-class AsyncPermissionSavedResource(AsyncAPIResource):
- """Operations under ``client.permission.saved`` (OpenCode ``permission.saved``)."""
-
- @cached_property
- def with_raw_response(self) -> AsyncPermissionSavedResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncPermissionSavedResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncPermissionSavedResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncPermissionSavedResourceWithStreamingResponse(self)
-
- async def list(
- self,
- *,
- project_id: ProjectID | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[PermissionSavedInfo]:
- """List saved permissions.
-
- Retrieve saved permissions, optionally filtered by project.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/permission/saved``
- """
- return cast(
- "builtins.list[PermissionSavedInfo]",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/permission/saved",
- query={"projectID": project_id},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[PermissionSavedInfo]",
- unwrap=True,
- ),
- )
-
- async def update(
- self,
- id: PermissionSavedID,
- *,
- action: str | NotGiven | None = NOT_GIVEN,
- resource: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PermissionSavedInfo:
- """Update saved permission.
-
- Change the action or resource of a saved permission. Fails with a conflict when the project already has a saved permission with the same action and resource.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- PermissionSavedNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- ConflictError: The server answered 409.
-
- ``PATCH /api/permission/saved/:id``
- """
- return cast(
- "PermissionSavedInfo",
- await self._client.request(
- RequestOptions(
- method="PATCH",
- path=f"/api/permission/saved/{encode_path_segment(id)}",
- body={"action": action, "resource": resource},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PermissionSavedInfo",
- unwrap=True,
- ),
- )
-
- async def remove(
- self,
- id: PermissionSavedID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Remove saved permission.
-
- Remove a saved permission by ID.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``DELETE /api/permission/saved/:id``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/permission/saved/{encode_path_segment(id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class AsyncPermissionSavedResourceWithRawResponse:
- """``client.permission.saved.with_raw_response``."""
-
- def __init__(self, resource: AsyncPermissionSavedResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
- self.update = async_to_raw_response_wrapper(resource.update)
- self.remove = async_to_raw_response_wrapper(resource.remove)
-
-
-class AsyncPermissionSavedResourceWithStreamingResponse:
- """``client.permission.saved.with_streaming_response``."""
-
- def __init__(self, resource: AsyncPermissionSavedResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.update = async_to_streamed_response_wrapper(resource.update)
- self.remove = async_to_streamed_response_wrapper(resource.remove)
-
-
-class AsyncPermissionResource(AsyncAPIResource):
- """Operations under ``client.permission`` (OpenCode ``permission``)."""
-
- @cached_property
- def request_(self) -> AsyncPermissionRequestResource:
- """Operations under ``client.permission.request``."""
- return AsyncPermissionRequestResource(self._client)
-
- @cached_property
- def saved(self) -> AsyncPermissionSavedResource:
- """Operations under ``client.permission.saved``."""
- return AsyncPermissionSavedResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> AsyncPermissionResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncPermissionResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncPermissionResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncPermissionResourceWithStreamingResponse(self)
-
- async def create(
- self,
- session_id: SessionID,
- *,
- action: str,
- resources: Sequence[str],
- id: PermissionID | NotGiven | None = NOT_GIVEN,
- save: Sequence[str] | NotGiven = NOT_GIVEN,
- metadata: Mapping[str, Any] | NotGiven = NOT_GIVEN,
- source: PermissionSource | NotGiven = NOT_GIVEN,
- agent: AgentID | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PermissionCreateResponse:
- """Create permission request.
-
- Evaluate and, when approval is required, create a permission request for a session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/permission``
- """
- return cast(
- "PermissionCreateResponse",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/permission",
- body={
- "id": id,
- "action": action,
- "resources": resources,
- "save": save,
- "metadata": metadata,
- "source": source,
- "agent": agent,
- },
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PermissionCreateResponse",
- unwrap=True,
- ),
- )
-
- async def list(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[PermissionRequest]:
- """List session permission requests.
-
- Retrieve pending permission requests owned by a session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/session/:sessionID/permission``
- """
- return cast(
- "builtins.list[PermissionRequest]",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}/permission",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[PermissionRequest]",
- unwrap=True,
- ),
- )
-
- async def get(
- self,
- session_id: SessionID,
- request_id: PermissionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PermissionRequest:
- """Get permission request.
-
- Retrieve a pending permission request owned by a session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- PermissionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/session/:sessionID/permission/:requestID``
- """
- return cast(
- "PermissionRequest",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}/permission/{encode_path_segment(request_id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PermissionRequest",
- unwrap=True,
- ),
- )
-
- async def reply(
- self,
- session_id: SessionID,
- request_id: PermissionID,
- *,
- decision: PermissionReply,
- message: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Reply to pending permission request.
-
- Respond to a pending permission request owned by a session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- PermissionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/permission/:requestID/reply``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/permission/{encode_path_segment(request_id)}/reply",
- body={"decision": decision, "message": message},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class AsyncPermissionResourceWithRawResponse:
- """``client.permission.with_raw_response``."""
-
- def __init__(self, resource: AsyncPermissionResource) -> None:
- self._resource = resource
- self.create = async_to_raw_response_wrapper(resource.create)
- self.list = async_to_raw_response_wrapper(resource.list)
- self.get = async_to_raw_response_wrapper(resource.get)
- self.reply = async_to_raw_response_wrapper(resource.reply)
-
- @cached_property
- def request_(self) -> AsyncPermissionRequestResourceWithRawResponse:
- """``client.permission.request.with_raw_response``."""
- return AsyncPermissionRequestResourceWithRawResponse(self._resource.request_)
-
- @cached_property
- def saved(self) -> AsyncPermissionSavedResourceWithRawResponse:
- """``client.permission.saved.with_raw_response``."""
- return AsyncPermissionSavedResourceWithRawResponse(self._resource.saved)
-
-
-class AsyncPermissionResourceWithStreamingResponse:
- """``client.permission.with_streaming_response``."""
-
- def __init__(self, resource: AsyncPermissionResource) -> None:
- self._resource = resource
- self.create = async_to_streamed_response_wrapper(resource.create)
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.get = async_to_streamed_response_wrapper(resource.get)
- self.reply = async_to_streamed_response_wrapper(resource.reply)
-
- @cached_property
- def request_(self) -> AsyncPermissionRequestResourceWithStreamingResponse:
- """``client.permission.request.with_streaming_response``."""
- return AsyncPermissionRequestResourceWithStreamingResponse(self._resource.request_)
-
- @cached_property
- def saved(self) -> AsyncPermissionSavedResourceWithStreamingResponse:
- """``client.permission.saved.with_streaming_response``."""
- return AsyncPermissionSavedResourceWithStreamingResponse(self._resource.saved)
diff --git a/src/opencode/resources/pty.py b/src/opencode/resources/pty.py
deleted file mode 100644
index b027611..0000000
--- a/src/opencode/resources/pty.py
+++ /dev/null
@@ -1,674 +0,0 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``pty`` resource."""
-
-from __future__ import annotations
-
-from collections.abc import Mapping, Sequence
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
-
-from .._base_client import RequestOptions
-from .._pty import AsyncPtyAttachMixin, PtyAttachMixin
-from .._qs import encode_path_segment
-from .._resource import AsyncAPIResource, SyncAPIResource
-from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
-)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- Location,
- PtyConnectTokenResponse,
- PtyCreateResponse,
- PtyGetResponse,
- PtyID,
- PtyListResponse,
- PtyUpdateResponse,
- PtyUpdateSize,
- )
-
-__all__ = ["PtyResource", "AsyncPtyResource"]
-
-
-class PtyConnectResource(SyncAPIResource):
- """Operations under ``client.pty.connect`` (OpenCode ``pty.connect``)."""
-
- @cached_property
- def with_raw_response(self) -> PtyConnectResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return PtyConnectResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> PtyConnectResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return PtyConnectResourceWithStreamingResponse(self)
-
- def token(
- self,
- pty_id: PtyID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- x_opencode_ticket: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PtyConnectTokenResponse:
- """Create PTY WebSocket token.
-
- Create a short-lived single-use ticket for opening a PTY WebSocket connection.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ForbiddenError: The server answered 403.
- PtyNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/pty/:ptyID/connect-token``
- """
- return cast(
- "PtyConnectTokenResponse",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/pty/{encode_path_segment(pty_id)}/connect-token",
- query={"location": self._location(location)},
- headers={"x-opencode-ticket": x_opencode_ticket},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PtyConnectTokenResponse",
- ),
- )
-
-
-class PtyConnectResourceWithRawResponse:
- """``client.pty.connect.with_raw_response``."""
-
- def __init__(self, resource: PtyConnectResource) -> None:
- self._resource = resource
- self.token = to_raw_response_wrapper(resource.token)
-
-
-class PtyConnectResourceWithStreamingResponse:
- """``client.pty.connect.with_streaming_response``."""
-
- def __init__(self, resource: PtyConnectResource) -> None:
- self._resource = resource
- self.token = to_streamed_response_wrapper(resource.token)
-
-
-class PtyResource(PtyAttachMixin):
- """Operations under ``client.pty`` (OpenCode ``pty``)."""
-
- @cached_property
- def connect(self) -> PtyConnectResource:
- """Operations under ``client.pty.connect``."""
- return PtyConnectResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> PtyResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return PtyResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> PtyResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return PtyResourceWithStreamingResponse(self)
-
- def list(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PtyListResponse:
- """List PTY sessions.
-
- List PTY sessions for a location, including exited sessions retained until removal.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/pty``
- """
- return cast(
- "PtyListResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/pty",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PtyListResponse",
- ),
- )
-
- def create(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- command: str | NotGiven = NOT_GIVEN,
- args: Sequence[str] | NotGiven = NOT_GIVEN,
- cwd: str | NotGiven = NOT_GIVEN,
- title: str | NotGiven = NOT_GIVEN,
- env: Mapping[str, str] | NotGiven = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PtyCreateResponse:
- """Create PTY session.
-
- Create a pseudo-terminal session for a location.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/pty``
- """
- return cast(
- "PtyCreateResponse",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/pty",
- query={"location": self._location(location)},
- body={"command": command, "args": args, "cwd": cwd, "title": title, "env": env},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PtyCreateResponse",
- ),
- )
-
- def get(
- self,
- pty_id: PtyID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PtyGetResponse:
- """Get PTY session.
-
- Get one PTY session, including its exit code once exited.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- PtyNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/pty/:ptyID``
- """
- return cast(
- "PtyGetResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/pty/{encode_path_segment(pty_id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PtyGetResponse",
- ),
- )
-
- def update(
- self,
- pty_id: PtyID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- title: str | NotGiven = NOT_GIVEN,
- size: PtyUpdateSize | NotGiven = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PtyUpdateResponse:
- """Update PTY session.
-
- Update the title or viewport size of one PTY session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- PtyNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``PUT /api/pty/:ptyID``
- """
- return cast(
- "PtyUpdateResponse",
- self._client.request(
- RequestOptions(
- method="PUT",
- path=f"/api/pty/{encode_path_segment(pty_id)}",
- query={"location": self._location(location)},
- body={"title": title, "size": size},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PtyUpdateResponse",
- ),
- )
-
- def remove(
- self,
- pty_id: PtyID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Remove PTY session.
-
- Terminate and remove one PTY session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- PtyNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``DELETE /api/pty/:ptyID``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/pty/{encode_path_segment(pty_id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class PtyResourceWithRawResponse:
- """``client.pty.with_raw_response``."""
-
- def __init__(self, resource: PtyResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
- self.create = to_raw_response_wrapper(resource.create)
- self.get = to_raw_response_wrapper(resource.get)
- self.update = to_raw_response_wrapper(resource.update)
- self.remove = to_raw_response_wrapper(resource.remove)
-
- @cached_property
- def connect(self) -> PtyConnectResourceWithRawResponse:
- """``client.pty.connect.with_raw_response``."""
- return PtyConnectResourceWithRawResponse(self._resource.connect)
-
-
-class PtyResourceWithStreamingResponse:
- """``client.pty.with_streaming_response``."""
-
- def __init__(self, resource: PtyResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
- self.create = to_streamed_response_wrapper(resource.create)
- self.get = to_streamed_response_wrapper(resource.get)
- self.update = to_streamed_response_wrapper(resource.update)
- self.remove = to_streamed_response_wrapper(resource.remove)
-
- @cached_property
- def connect(self) -> PtyConnectResourceWithStreamingResponse:
- """``client.pty.connect.with_streaming_response``."""
- return PtyConnectResourceWithStreamingResponse(self._resource.connect)
-
-
-class AsyncPtyConnectResource(AsyncAPIResource):
- """Operations under ``client.pty.connect`` (OpenCode ``pty.connect``)."""
-
- @cached_property
- def with_raw_response(self) -> AsyncPtyConnectResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncPtyConnectResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncPtyConnectResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncPtyConnectResourceWithStreamingResponse(self)
-
- async def token(
- self,
- pty_id: PtyID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- x_opencode_ticket: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PtyConnectTokenResponse:
- """Create PTY WebSocket token.
-
- Create a short-lived single-use ticket for opening a PTY WebSocket connection.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- ForbiddenError: The server answered 403.
- PtyNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/pty/:ptyID/connect-token``
- """
- return cast(
- "PtyConnectTokenResponse",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/pty/{encode_path_segment(pty_id)}/connect-token",
- query={"location": self._location(location)},
- headers={"x-opencode-ticket": x_opencode_ticket},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PtyConnectTokenResponse",
- ),
- )
-
-
-class AsyncPtyConnectResourceWithRawResponse:
- """``client.pty.connect.with_raw_response``."""
-
- def __init__(self, resource: AsyncPtyConnectResource) -> None:
- self._resource = resource
- self.token = async_to_raw_response_wrapper(resource.token)
-
-
-class AsyncPtyConnectResourceWithStreamingResponse:
- """``client.pty.connect.with_streaming_response``."""
-
- def __init__(self, resource: AsyncPtyConnectResource) -> None:
- self._resource = resource
- self.token = async_to_streamed_response_wrapper(resource.token)
-
-
-class AsyncPtyResource(AsyncPtyAttachMixin):
- """Operations under ``client.pty`` (OpenCode ``pty``)."""
-
- @cached_property
- def connect(self) -> AsyncPtyConnectResource:
- """Operations under ``client.pty.connect``."""
- return AsyncPtyConnectResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> AsyncPtyResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncPtyResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncPtyResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncPtyResourceWithStreamingResponse(self)
-
- async def list(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PtyListResponse:
- """List PTY sessions.
-
- List PTY sessions for a location, including exited sessions retained until removal.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/pty``
- """
- return cast(
- "PtyListResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/pty",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PtyListResponse",
- ),
- )
-
- async def create(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- command: str | NotGiven = NOT_GIVEN,
- args: Sequence[str] | NotGiven = NOT_GIVEN,
- cwd: str | NotGiven = NOT_GIVEN,
- title: str | NotGiven = NOT_GIVEN,
- env: Mapping[str, str] | NotGiven = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PtyCreateResponse:
- """Create PTY session.
-
- Create a pseudo-terminal session for a location.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/pty``
- """
- return cast(
- "PtyCreateResponse",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/pty",
- query={"location": self._location(location)},
- body={"command": command, "args": args, "cwd": cwd, "title": title, "env": env},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PtyCreateResponse",
- ),
- )
-
- async def get(
- self,
- pty_id: PtyID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PtyGetResponse:
- """Get PTY session.
-
- Get one PTY session, including its exit code once exited.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- PtyNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/pty/:ptyID``
- """
- return cast(
- "PtyGetResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/pty/{encode_path_segment(pty_id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PtyGetResponse",
- ),
- )
-
- async def update(
- self,
- pty_id: PtyID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- title: str | NotGiven = NOT_GIVEN,
- size: PtyUpdateSize | NotGiven = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> PtyUpdateResponse:
- """Update PTY session.
-
- Update the title or viewport size of one PTY session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- PtyNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``PUT /api/pty/:ptyID``
- """
- return cast(
- "PtyUpdateResponse",
- await self._client.request(
- RequestOptions(
- method="PUT",
- path=f"/api/pty/{encode_path_segment(pty_id)}",
- query={"location": self._location(location)},
- body={"title": title, "size": size},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="PtyUpdateResponse",
- ),
- )
-
- async def remove(
- self,
- pty_id: PtyID,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Remove PTY session.
-
- Terminate and remove one PTY session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- PtyNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``DELETE /api/pty/:ptyID``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/pty/{encode_path_segment(pty_id)}",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class AsyncPtyResourceWithRawResponse:
- """``client.pty.with_raw_response``."""
-
- def __init__(self, resource: AsyncPtyResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
- self.create = async_to_raw_response_wrapper(resource.create)
- self.get = async_to_raw_response_wrapper(resource.get)
- self.update = async_to_raw_response_wrapper(resource.update)
- self.remove = async_to_raw_response_wrapper(resource.remove)
-
- @cached_property
- def connect(self) -> AsyncPtyConnectResourceWithRawResponse:
- """``client.pty.connect.with_raw_response``."""
- return AsyncPtyConnectResourceWithRawResponse(self._resource.connect)
-
-
-class AsyncPtyResourceWithStreamingResponse:
- """``client.pty.with_streaming_response``."""
-
- def __init__(self, resource: AsyncPtyResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.create = async_to_streamed_response_wrapper(resource.create)
- self.get = async_to_streamed_response_wrapper(resource.get)
- self.update = async_to_streamed_response_wrapper(resource.update)
- self.remove = async_to_streamed_response_wrapper(resource.remove)
-
- @cached_property
- def connect(self) -> AsyncPtyConnectResourceWithStreamingResponse:
- """``client.pty.connect.with_streaming_response``."""
- return AsyncPtyConnectResourceWithStreamingResponse(self._resource.connect)
diff --git a/src/opencode/resources/session.py b/src/opencode/resources/session.py
deleted file mode 100644
index 07ff0cf..0000000
--- a/src/opencode/resources/session.py
+++ /dev/null
@@ -1,4321 +0,0 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``session`` resource."""
-
-from __future__ import annotations
-
-import builtins
-from collections.abc import Mapping, Sequence
-from functools import cached_property
-from typing import TYPE_CHECKING, Any, Literal, cast
-
-from .._base_client import RequestOptions
-from .._pagination import AsyncPaginator, SyncCursorPage
-from .._qs import encode_path_segment
-from .._resource import AsyncAPIResource, SyncAPIResource
-from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
-)
-from .._streaming import AsyncStream, Stream
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, OpenEnum, Query, Timeout
-from ..lib._session import AsyncSessionHelpers, SessionHelpers
-
-if TYPE_CHECKING:
- from ..types import (
- AbsolutePath,
- AgentID,
- EventSeq,
- FileDiffInfo,
- FormAnswer,
- FormDetail,
- FormFields,
- FormID,
- FormInfo,
- FormMetadata,
- InstructionEntryInfo,
- InstructionEntryKey,
- LocationPublicRef,
- ModelRef,
- PermissionRuleset,
- ProjectID,
- PromptAgentAttachment,
- PromptInputFileAttachment,
- PromptInputSkillAttachment,
- RelativePath,
- SessionGenerateResponseData,
- SessionID,
- SessionInboxCompaction,
- SessionInboxDelivery,
- SessionInboxInfo,
- SessionInboxSynthetic,
- SessionInboxUser,
- SessionInfo,
- SessionInterruptResponse,
- SessionLogItem,
- SessionMessageID,
- SessionMessageInfo,
- SessionMetadata,
- SessionRevert,
- SessionsCursor,
- SessionStatsInfo,
- SessionTransferData,
- SkillID,
- )
-
-__all__ = ["SessionResource", "AsyncSessionResource"]
-
-
-class SessionRevertResource(SyncAPIResource):
- """Operations under ``client.session.revert`` (OpenCode ``session.revert``)."""
-
- @cached_property
- def with_raw_response(self) -> SessionRevertResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return SessionRevertResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> SessionRevertResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return SessionRevertResourceWithStreamingResponse(self)
-
- def stage(
- self,
- session_id: SessionID,
- *,
- message_id: SessionMessageID,
- files: bool | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionRevert:
- """Stage session revert.
-
- Stage or move a reversible session boundary and optionally apply its file changes.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- MessageNotFoundError: The server answered 404.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- SessionBusyError: The server answered 409.
- UnknownError: The server answered 500.
-
- ``POST /api/session/:sessionID/revert/stage``
- """
- return cast(
- "SessionRevert",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/revert/stage",
- body={"messageID": message_id, "files": files},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionRevert",
- unwrap=True,
- ),
- )
-
- def clear(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Clear staged revert.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- SessionBusyError: The server answered 409.
- UnknownError: The server answered 500.
-
- ``DELETE /api/session/:sessionID/revert``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/session/{encode_path_segment(session_id)}/revert",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def commit(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Commit staged revert.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- SessionBusyError: The server answered 409.
-
- ``POST /api/session/:sessionID/revert/commit``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/revert/commit",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class SessionRevertResourceWithRawResponse:
- """``client.session.revert.with_raw_response``."""
-
- def __init__(self, resource: SessionRevertResource) -> None:
- self._resource = resource
- self.stage = to_raw_response_wrapper(resource.stage)
- self.clear = to_raw_response_wrapper(resource.clear)
- self.commit = to_raw_response_wrapper(resource.commit)
-
-
-class SessionRevertResourceWithStreamingResponse:
- """``client.session.revert.with_streaming_response``."""
-
- def __init__(self, resource: SessionRevertResource) -> None:
- self._resource = resource
- self.stage = to_streamed_response_wrapper(resource.stage)
- self.clear = to_streamed_response_wrapper(resource.clear)
- self.commit = to_streamed_response_wrapper(resource.commit)
-
-
-class SessionInboxResource(SyncAPIResource):
- """Operations under ``client.session.inbox`` (OpenCode ``session.inbox``)."""
-
- @cached_property
- def with_raw_response(self) -> SessionInboxResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return SessionInboxResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> SessionInboxResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return SessionInboxResourceWithStreamingResponse(self)
-
- def list(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[SessionInboxInfo]:
- """List session inbox.
-
- List durable enqueued session work not yet delivered, ordered by enqueue sequence. Includes user, synthetic, compaction, and move items.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
-
- ``GET /api/session/:sessionID/inbox``
- """
- return cast(
- "builtins.list[SessionInboxInfo]",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}/inbox",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[SessionInboxInfo]",
- unwrap=True,
- ),
- )
-
- def cancel(
- self,
- session_id: SessionID,
- inbox_id: SessionMessageID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Cancel inbox input.
-
- Cancel an inbox item that has not yet been delivered. Unavailable items are a no-op.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
-
- ``DELETE /api/session/:sessionID/inbox/:inboxID``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/session/{encode_path_segment(session_id)}/inbox/{encode_path_segment(inbox_id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def update(
- self,
- session_id: SessionID,
- inbox_id: SessionMessageID,
- *,
- delivery: SessionInboxDelivery,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Update inbox item.
-
- Change a pending inbox item's delivery mode. Steering wakes session execution.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- ConflictError: The server answered 409.
-
- ``PATCH /api/session/:sessionID/inbox/:inboxID``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="PATCH",
- path=f"/api/session/{encode_path_segment(session_id)}/inbox/{encode_path_segment(inbox_id)}",
- body={"delivery": delivery},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class SessionInboxResourceWithRawResponse:
- """``client.session.inbox.with_raw_response``."""
-
- def __init__(self, resource: SessionInboxResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
- self.cancel = to_raw_response_wrapper(resource.cancel)
- self.update = to_raw_response_wrapper(resource.update)
-
-
-class SessionInboxResourceWithStreamingResponse:
- """``client.session.inbox.with_streaming_response``."""
-
- def __init__(self, resource: SessionInboxResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
- self.cancel = to_streamed_response_wrapper(resource.cancel)
- self.update = to_streamed_response_wrapper(resource.update)
-
-
-class SessionInstructionsEntryResource(SyncAPIResource):
- """Operations under ``client.session.instructions.entry`` (OpenCode ``session.instructions.entry``)."""
-
- @cached_property
- def with_raw_response(self) -> SessionInstructionsEntryResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return SessionInstructionsEntryResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> SessionInstructionsEntryResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return SessionInstructionsEntryResourceWithStreamingResponse(self)
-
- def list(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[InstructionEntryInfo]:
- """List instruction entries.
-
- List API-managed instruction entries attached to the session.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/experimental/session/:sessionID/instructions/entries``
- """
- return cast(
- "builtins.list[InstructionEntryInfo]",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/instructions/entries",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[InstructionEntryInfo]",
- unwrap=True,
- ),
- )
-
- def put(
- self,
- session_id: SessionID,
- key: InstructionEntryKey,
- *,
- value: Any,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Put instruction entry.
-
- Attach or replace one durable instruction entry. Changes announce as updates at the next step boundary.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- InstructionEntryValueTooLargeError: The server answered 413.
-
- ``PUT /api/experimental/session/:sessionID/instructions/entries/:key``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="PUT",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/instructions/entries/{encode_path_segment(key)}",
- body={"value": value},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def remove(
- self,
- session_id: SessionID,
- key: InstructionEntryKey,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Remove instruction entry.
-
- Remove one instruction entry; the removal is announced to the model at the next step boundary.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``DELETE /api/experimental/session/:sessionID/instructions/entries/:key``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/instructions/entries/{encode_path_segment(key)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class SessionInstructionsEntryResourceWithRawResponse:
- """``client.session.instructions.entry.with_raw_response``."""
-
- def __init__(self, resource: SessionInstructionsEntryResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
- self.put = to_raw_response_wrapper(resource.put)
- self.remove = to_raw_response_wrapper(resource.remove)
-
-
-class SessionInstructionsEntryResourceWithStreamingResponse:
- """``client.session.instructions.entry.with_streaming_response``."""
-
- def __init__(self, resource: SessionInstructionsEntryResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
- self.put = to_streamed_response_wrapper(resource.put)
- self.remove = to_streamed_response_wrapper(resource.remove)
-
-
-class SessionInstructionsResource(SyncAPIResource):
- """Operations under ``client.session.instructions`` (OpenCode ``session.instructions``)."""
-
- @cached_property
- def entry(self) -> SessionInstructionsEntryResource:
- """Operations under ``client.session.instructions.entry``."""
- return SessionInstructionsEntryResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> SessionInstructionsResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return SessionInstructionsResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> SessionInstructionsResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return SessionInstructionsResourceWithStreamingResponse(self)
-
-
-class SessionInstructionsResourceWithRawResponse:
- """``client.session.instructions.with_raw_response``."""
-
- def __init__(self, resource: SessionInstructionsResource) -> None:
- self._resource = resource
-
- @cached_property
- def entry(self) -> SessionInstructionsEntryResourceWithRawResponse:
- """``client.session.instructions.entry.with_raw_response``."""
- return SessionInstructionsEntryResourceWithRawResponse(self._resource.entry)
-
-
-class SessionInstructionsResourceWithStreamingResponse:
- """``client.session.instructions.with_streaming_response``."""
-
- def __init__(self, resource: SessionInstructionsResource) -> None:
- self._resource = resource
-
- @cached_property
- def entry(self) -> SessionInstructionsEntryResourceWithStreamingResponse:
- """``client.session.instructions.entry.with_streaming_response``."""
- return SessionInstructionsEntryResourceWithStreamingResponse(self._resource.entry)
-
-
-class SessionMessageResource(SyncAPIResource):
- """Operations under ``client.session.message`` (OpenCode ``session.message``)."""
-
- @cached_property
- def with_raw_response(self) -> SessionMessageResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return SessionMessageResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> SessionMessageResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return SessionMessageResourceWithStreamingResponse(self)
-
- def get(
- self,
- session_id: SessionID,
- message_id: SessionMessageID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionMessageInfo:
- """Get session message.
-
- Retrieve one projected message owned by the Session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- MessageNotFoundError: The server answered 404.
-
- ``GET /api/session/:sessionID/message/:messageID``
- """
- return cast(
- "SessionMessageInfo",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}/message/{encode_path_segment(message_id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionMessageInfo",
- unwrap=True,
- ),
- )
-
-
-class SessionMessageResourceWithRawResponse:
- """``client.session.message.with_raw_response``."""
-
- def __init__(self, resource: SessionMessageResource) -> None:
- self._resource = resource
- self.get = to_raw_response_wrapper(resource.get)
-
-
-class SessionMessageResourceWithStreamingResponse:
- """``client.session.message.with_streaming_response``."""
-
- def __init__(self, resource: SessionMessageResource) -> None:
- self._resource = resource
- self.get = to_streamed_response_wrapper(resource.get)
-
-
-class SessionFormResource(SyncAPIResource):
- """Operations under ``client.session.form`` (OpenCode ``session.form``)."""
-
- @cached_property
- def with_raw_response(self) -> SessionFormResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return SessionFormResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> SessionFormResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return SessionFormResourceWithStreamingResponse(self)
-
- def list(
- self,
- session_id: str,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[FormInfo]:
- """List session forms.
-
- Retrieve pending forms for a session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/session/:sessionID/form``
- """
- return cast(
- "builtins.list[FormInfo]",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}/form",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[FormInfo]",
- unwrap=True,
- ),
- )
-
- def create(
- self,
- session_id: str,
- *,
- title: str,
- fields: FormFields,
- id: FormID | NotGiven | None = NOT_GIVEN,
- metadata: FormMetadata | NotGiven = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> FormInfo:
- """Create session form.
-
- Create a form for a session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- ConflictError: The server answered 409.
-
- ``POST /api/session/:sessionID/form``
- """
- return cast(
- "FormInfo",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/form",
- body={"id": id, "title": title, "metadata": metadata, "fields": fields},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="FormInfo",
- unwrap=True,
- ),
- )
-
- def get(
- self,
- session_id: str,
- form_id: FormID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> FormDetail:
- """Get session form.
-
- Retrieve a form and its current state for a session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- FormNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/session/:sessionID/form/:formID``
- """
- return cast(
- "FormDetail",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}/form/{encode_path_segment(form_id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="FormDetail",
- unwrap=True,
- ),
- )
-
- def reply(
- self,
- session_id: str,
- form_id: FormID,
- *,
- answer: FormAnswer,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Reply to form.
-
- Submit an answer to a pending form.
-
- Raises:
- FormInvalidAnswerError: The server answered 400.
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- FormNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- FormAlreadySettledError: The server answered 409.
-
- ``POST /api/session/:sessionID/form/:formID/reply``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/form/{encode_path_segment(form_id)}/reply",
- body={"answer": answer},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def cancel(
- self,
- session_id: str,
- form_id: FormID,
- *,
- message: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Cancel form.
-
- Cancel a pending form, optionally telling the asker why it was not answered.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- FormNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- FormAlreadySettledError: The server answered 409.
-
- ``DELETE /api/session/:sessionID/form/:formID``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/session/{encode_path_segment(session_id)}/form/{encode_path_segment(form_id)}",
- query={"message": message},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class SessionFormResourceWithRawResponse:
- """``client.session.form.with_raw_response``."""
-
- def __init__(self, resource: SessionFormResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
- self.create = to_raw_response_wrapper(resource.create)
- self.get = to_raw_response_wrapper(resource.get)
- self.reply = to_raw_response_wrapper(resource.reply)
- self.cancel = to_raw_response_wrapper(resource.cancel)
-
-
-class SessionFormResourceWithStreamingResponse:
- """``client.session.form.with_streaming_response``."""
-
- def __init__(self, resource: SessionFormResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
- self.create = to_streamed_response_wrapper(resource.create)
- self.get = to_streamed_response_wrapper(resource.get)
- self.reply = to_streamed_response_wrapper(resource.reply)
- self.cancel = to_streamed_response_wrapper(resource.cancel)
-
-
-class SessionResource(SessionHelpers):
- """Operations under ``client.session`` (OpenCode ``session``)."""
-
- @cached_property
- def revert(self) -> SessionRevertResource:
- """Operations under ``client.session.revert``."""
- return SessionRevertResource(self._client)
-
- @cached_property
- def inbox(self) -> SessionInboxResource:
- """Operations under ``client.session.inbox``."""
- return SessionInboxResource(self._client)
-
- @cached_property
- def instructions(self) -> SessionInstructionsResource:
- """Operations under ``client.session.instructions``."""
- return SessionInstructionsResource(self._client)
-
- @cached_property
- def message(self) -> SessionMessageResource:
- """Operations under ``client.session.message``."""
- return SessionMessageResource(self._client)
-
- @cached_property
- def form(self) -> SessionFormResource:
- """Operations under ``client.session.form``."""
- return SessionFormResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> SessionResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return SessionResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> SessionResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return SessionResourceWithStreamingResponse(self)
-
- def list(
- self,
- *,
- limit: int | NotGiven | None = NOT_GIVEN,
- order: OpenEnum[Literal["asc", "desc"]] | NotGiven | None = NOT_GIVEN,
- search: str | NotGiven | None = NOT_GIVEN,
- parent_id: SessionID | NotGiven | None = NOT_GIVEN,
- archived: bool | NotGiven | None = NOT_GIVEN,
- directory: AbsolutePath | NotGiven | None = NOT_GIVEN,
- project: ProjectID | NotGiven | None = NOT_GIVEN,
- subpath: RelativePath | NotGiven | None = NOT_GIVEN,
- cursor: SessionsCursor | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SyncCursorPage[SessionInfo]:
- """List sessions.
-
- Retrieve sessions in the requested order. Items keep that order across pages; use cursor.next or cursor.previous to move through the ordered list.
-
- Args:
- limit: Maximum number of sessions to return. Defaults to the newest 50 sessions.
- order: Session order for the first page. Use desc for newest first or asc for oldest first.
- parent_id: Filter by parent session. Use null to return only root sessions.
- archived: Filter by archive state. Use true to return only archived sessions or false to exclude them. Omit to include both.
-
- Raises:
- InvalidCursorError: The server answered 400.
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
-
- ``GET /api/session``
- """
- return self._client.page(
- RequestOptions(
- method="GET",
- path="/api/session",
- query={
- "limit": limit,
- "order": order,
- "search": search,
- "parentID": parent_id,
- "archived": archived,
- "directory": directory,
- "project": project,
- "subpath": subpath,
- "cursor": cursor,
- },
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionsResponse",
- )
-
- def stats(
- self,
- *,
- from_: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]] | NotGiven | None = NOT_GIVEN,
- to: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]] | NotGiven | None = NOT_GIVEN,
- project: ProjectID | NotGiven | None = NOT_GIVEN,
- timezone: str | NotGiven | None = NOT_GIVEN,
- tools: OpenEnum[Literal["none", "summary", "detail"]] | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionStatsInfo:
- """Get session statistics.
-
- Aggregate local session activity, usage, and tool reliability for a time range.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
-
- ``GET /api/experimental/session/stats``
- """
- return cast(
- "SessionStatsInfo",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/experimental/session/stats",
- query={"from": from_, "to": to, "project": project, "timezone": timezone, "tools": tools},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionStatsInfo",
- unwrap=True,
- ),
- )
-
- def create(
- self,
- *,
- id: SessionID | NotGiven | None = NOT_GIVEN,
- parent_id: SessionID | NotGiven | None = NOT_GIVEN,
- title: str | NotGiven | None = NOT_GIVEN,
- agent: AgentID | NotGiven | None = NOT_GIVEN,
- model: ModelRef | NotGiven | None = NOT_GIVEN,
- location: LocationPublicRef | NotGiven | None = NOT_GIVEN,
- metadata: SessionMetadata | NotGiven | None = NOT_GIVEN,
- permissions: PermissionRuleset | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionInfo:
- """Create session.
-
- Create a session at the requested location. A parentID creates a linked child session at its parent's location.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
-
- ``POST /api/session``
- """
- return cast(
- "SessionInfo",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/session",
- body={
- "id": id,
- "parentID": parent_id,
- "title": title,
- "agent": agent,
- "model": model,
- "location": self._location(location),
- "metadata": metadata,
- "permissions": permissions,
- },
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionInfo",
- unwrap=True,
- ),
- )
-
- def import_(
- self,
- *,
- info: SessionInfo,
- messages: Sequence[Any],
- location: LocationPublicRef | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionInfo:
- """Import session.
-
- Import a projected session transcript at the requested location. Accepts transcripts exported by earlier releases. If parentID is supplied, the parent session must already exist; import parents before children.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- ConflictError: The server answered 409.
-
- ``POST /api/experimental/session/import``
- """
- return cast(
- "SessionInfo",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/experimental/session/import",
- body={"info": info, "messages": messages, "location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionInfo",
- unwrap=True,
- ),
- )
-
- def export(
- self,
- session_id: SessionID,
- *,
- sanitize: bool | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionTransferData:
- """Export session.
-
- Export a complete projected session transcript.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- UnknownError: The server answered 500.
-
- ``GET /api/experimental/session/:sessionID/export``
- """
- return cast(
- "SessionTransferData",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/export",
- query={"sanitize": sanitize},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionTransferData",
- unwrap=True,
- ),
- )
-
- def active(
- self,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.dict[str, Any]:
- """List active sessions.
-
- Retrieve foreground Session drains currently owned by this OpenCode process. Sessions absent from the result are inactive.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
-
- ``GET /api/session/active``
- """
- return cast(
- "builtins.dict[str, Any]",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/session/active",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.dict[str, Any]",
- unwrap=True,
- ),
- )
-
- def get(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionInfo:
- """Get session.
-
- Retrieve a session by ID.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
-
- ``GET /api/session/:sessionID``
- """
- return cast(
- "SessionInfo",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionInfo",
- unwrap=True,
- ),
- )
-
- def remove(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Delete session.
-
- Delete a session and its child sessions.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
-
- ``DELETE /api/session/:sessionID``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/session/{encode_path_segment(session_id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def fork(
- self,
- session_id: SessionID,
- *,
- before: SessionMessageID | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionInfo:
- """Fork session.
-
- Create a child session by copying projected history before a message. Omit before to copy the full history.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- MessageNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/fork``
- """
- return cast(
- "SessionInfo",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/fork",
- body={"before": before},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionInfo",
- unwrap=True,
- ),
- )
-
- def switch_agent(
- self,
- session_id: SessionID,
- *,
- agent: AgentID,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Switch session agent.
-
- Switch the agent used by subsequent provider turns.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/agent``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/agent",
- body={"agent": agent},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def switch_model(
- self,
- session_id: SessionID,
- *,
- model: ModelRef,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Switch session model.
-
- Switch the model used by subsequent provider turns.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/model``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/model",
- body={"model": model},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def update(
- self,
- session_id: SessionID,
- *,
- title: str | NotGiven | None = NOT_GIVEN,
- metadata: SessionMetadata | NotGiven | None = NOT_GIVEN,
- permissions: PermissionRuleset | NotGiven | None = NOT_GIVEN,
- archived: bool | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Update session.
-
- Update mutable session properties.
-
- Args:
- archived: Archive the session with true or restore it with false. Repeating the current state is a no-op.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``PATCH /api/session/:sessionID``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="PATCH",
- path=f"/api/session/{encode_path_segment(session_id)}",
- body={"title": title, "metadata": metadata, "permissions": permissions, "archived": archived},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def move(
- self,
- session_id: SessionID,
- *,
- directory: AbsolutePath,
- delivery: SessionInboxDelivery | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Move session.
-
- Move a session to another project directory at the requested delivery boundary.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/move``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/move",
- body={"directory": directory, "delivery": delivery},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def prompt(
- self,
- session_id: SessionID,
- *,
- text: str,
- id: SessionMessageID | NotGiven | None = NOT_GIVEN,
- files: Sequence[PromptInputFileAttachment] | NotGiven = NOT_GIVEN,
- agents: Sequence[PromptAgentAttachment] | NotGiven = NOT_GIVEN,
- skills: Sequence[PromptInputSkillAttachment] | NotGiven = NOT_GIVEN,
- metadata: Mapping[str, Any] | NotGiven = NOT_GIVEN,
- delivery: SessionInboxDelivery | NotGiven | None = NOT_GIVEN,
- resume: bool | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionInboxUser:
- """Send message.
-
- Durably admit one session input and schedule agent-loop execution unless resume is false.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- ConflictError: The server answered 409.
-
- ``POST /api/session/:sessionID/prompt``
- """
- return cast(
- "SessionInboxUser",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/prompt",
- body={
- "id": id,
- "text": text,
- "files": files,
- "agents": agents,
- "skills": skills,
- "metadata": metadata,
- "delivery": delivery,
- "resume": resume,
- },
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionInboxUser",
- unwrap=True,
- ),
- )
-
- def command(
- self,
- session_id: SessionID,
- *,
- name: str,
- text: str,
- files: Sequence[PromptInputFileAttachment] | NotGiven = NOT_GIVEN,
- agents: Sequence[PromptAgentAttachment] | NotGiven = NOT_GIVEN,
- skills: Sequence[PromptInputSkillAttachment] | NotGiven = NOT_GIVEN,
- delivery: SessionInboxDelivery | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Run command.
-
- Execute a slash command callback immediately.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- CommandNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- CommandExecutionError: The server answered 500.
-
- ``POST /api/session/:sessionID/command``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/command",
- body={
- "name": name,
- "text": text,
- "files": files,
- "agents": agents,
- "skills": skills,
- "delivery": delivery,
- },
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def skill(
- self,
- session_id: SessionID,
- *,
- id: SkillID,
- resume: bool | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Activate skill.
-
- Activate a skill for a session by appending a skill message and resuming execution.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- SkillNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/experimental/session/:sessionID/skill``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/skill",
- body={"id": id, "resume": resume},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def synthetic(
- self,
- session_id: SessionID,
- *,
- text: str,
- id: SessionMessageID | NotGiven | None = NOT_GIVEN,
- description: str | NotGiven | None = NOT_GIVEN,
- metadata: Mapping[str, Any] | NotGiven = NOT_GIVEN,
- delivery: SessionInboxDelivery | NotGiven | None = NOT_GIVEN,
- resume: bool | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionInboxSynthetic:
- """Add synthetic message.
-
- Durably admit synthetic session input and schedule execution unless resume is false.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- ConflictError: The server answered 409.
-
- ``POST /api/session/:sessionID/synthetic``
- """
- return cast(
- "SessionInboxSynthetic",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/synthetic",
- body={
- "id": id,
- "text": text,
- "description": description,
- "metadata": metadata,
- "delivery": delivery,
- "resume": resume,
- },
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionInboxSynthetic",
- unwrap=True,
- ),
- )
-
- def shell(
- self,
- session_id: SessionID,
- *,
- command: str,
- id: SessionMessageID | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Run shell command.
-
- Execute one shell command in the session's working directory. Emits a shell.started event before execution and a shell.ended event with the merged output after.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/shell``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/shell",
- body={"id": id, "command": command},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def compact(
- self,
- session_id: SessionID,
- *,
- id: SessionMessageID | NotGiven | None = NOT_GIVEN,
- delivery: SessionInboxDelivery | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionInboxCompaction:
- """Compact session.
-
- Durably admit a session compaction request. Steers by default: it runs at the next step boundary instead of waiting behind queued prompts.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- ConflictError: The server answered 409.
-
- ``POST /api/session/:sessionID/compact``
- """
- return cast(
- "SessionInboxCompaction",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/compact",
- body={"id": id, "delivery": delivery},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionInboxCompaction",
- unwrap=True,
- ),
- )
-
- def wait(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Wait for session.
-
- Wait for a session agent loop to become idle.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``POST /api/experimental/session/:sessionID/wait``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/wait",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def context(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[SessionMessageInfo]:
- """Get session context.
-
- Retrieve the active context messages for a session (all messages after the last compaction).
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- UnknownError: The server answered 500.
-
- ``GET /api/session/:sessionID/context``
- """
- return cast(
- "builtins.list[SessionMessageInfo]",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}/context",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[SessionMessageInfo]",
- unwrap=True,
- ),
- )
-
- def diff(
- self,
- session_id: SessionID,
- *,
- from_: SessionMessageID | NotGiven | None = NOT_GIVEN,
- to: SessionMessageID | NotGiven | None = NOT_GIVEN,
- context: int | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[FileDiffInfo]:
- """Diff session turns.
-
- Structured per-file diffs of the files a turn changed. A turn runs from the first prompt after the session was last idle until its next idle marker, so prompts steered in while it was busy belong to the same turn; `to` extends the range through a later turn. Compares the range's first recorded snapshot with its last; a step still running in the active session compares against the working copy. Ranges that span a location change are rejected. In sessions without any idle marker, a prompt's turn spans until the next user message.
-
- Args:
- from_: User message whose turn to diff. Defaults to the turn of the newest user message.
- to: Later user message whose turn ends the range. Defaults to the turn of `from` alone.
- context: Unchanged lines around each hunk. Omit for full-file patches.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- MessageNotFoundError: The server answered 404.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- UnknownError: The server answered 500.
-
- ``GET /api/session/:sessionID/diff``
- """
- return cast(
- "builtins.list[FileDiffInfo]",
- self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}/diff",
- query={"from": from_, "to": to, "context": context},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[FileDiffInfo]",
- unwrap=True,
- ),
- )
-
- def generate(
- self,
- session_id: SessionID,
- *,
- prompt: str,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionGenerateResponseData:
- """Generate text from session context.
-
- Generate transient text from the current session context without mutating session history.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``POST /api/session/:sessionID/generate``
- """
- return cast(
- "SessionGenerateResponseData",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/generate",
- body={"prompt": prompt},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionGenerateResponseData",
- unwrap=True,
- ),
- )
-
- def log(
- self,
- session_id: SessionID,
- *,
- after: EventSeq | NotGiven | None = NOT_GIVEN,
- follow: bool | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> Stream[SessionLogItem]:
- """Read the session log.
-
- Experimental durable session event log. Reads events after an exclusive aggregate sequence and continues with live events when follow=true.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
-
- ``GET /api/experimental/session/:sessionID/log``
- """
- return self._client.stream(
- RequestOptions(
- method="GET",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/log",
- query={"after": after, "follow": follow},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- item_type="SessionLogItem",
- )
-
- def interrupt(
- self,
- session_id: SessionID,
- *,
- resume: bool | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionInterruptResponse:
- """Interrupt session execution.
-
- Interrupt active execution owned by this OpenCode process. Returns interrupted=true when an active execution was interrupted and false for the idle no-op. When resume=true, execution resumes pending steering input and next-in-line control items (manual compaction, moves) while queued prompts remain parked.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/interrupt``
- """
- return cast(
- "SessionInterruptResponse",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/interrupt",
- query={"resume": resume},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionInterruptResponse",
- ),
- )
-
- def background(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Background blocking session tools.
-
- Move active foreground backgroundable tools for this session into background observation. Idle requests are a no-op.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/background``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/background",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def environment(
- self,
- session_id: SessionID,
- *,
- variables: Mapping[str, str],
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Set session environment.
-
- Replace the process environment used by local shell commands for this session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
-
- ``PUT /api/session/:sessionID/environment``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="PUT",
- path=f"/api/session/{encode_path_segment(session_id)}/environment",
- body={"variables": variables},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def view(
- self,
- session_id: SessionID,
- *,
- idle: float,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """View session.
-
- Mark the idle transition observed by the viewer as viewed.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/view``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/view",
- body={"idle": idle},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class SessionResourceWithRawResponse:
- """``client.session.with_raw_response``."""
-
- def __init__(self, resource: SessionResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
- self.stats = to_raw_response_wrapper(resource.stats)
- self.create = to_raw_response_wrapper(resource.create)
- self.import_ = to_raw_response_wrapper(resource.import_)
- self.export = to_raw_response_wrapper(resource.export)
- self.active = to_raw_response_wrapper(resource.active)
- self.get = to_raw_response_wrapper(resource.get)
- self.remove = to_raw_response_wrapper(resource.remove)
- self.fork = to_raw_response_wrapper(resource.fork)
- self.switch_agent = to_raw_response_wrapper(resource.switch_agent)
- self.switch_model = to_raw_response_wrapper(resource.switch_model)
- self.update = to_raw_response_wrapper(resource.update)
- self.move = to_raw_response_wrapper(resource.move)
- self.prompt = to_raw_response_wrapper(resource.prompt)
- self.command = to_raw_response_wrapper(resource.command)
- self.skill = to_raw_response_wrapper(resource.skill)
- self.synthetic = to_raw_response_wrapper(resource.synthetic)
- self.shell = to_raw_response_wrapper(resource.shell)
- self.compact = to_raw_response_wrapper(resource.compact)
- self.wait = to_raw_response_wrapper(resource.wait)
- self.context = to_raw_response_wrapper(resource.context)
- self.diff = to_raw_response_wrapper(resource.diff)
- self.generate = to_raw_response_wrapper(resource.generate)
- self.log = to_raw_response_wrapper(resource.log)
- self.interrupt = to_raw_response_wrapper(resource.interrupt)
- self.background = to_raw_response_wrapper(resource.background)
- self.environment = to_raw_response_wrapper(resource.environment)
- self.view = to_raw_response_wrapper(resource.view)
-
- @cached_property
- def revert(self) -> SessionRevertResourceWithRawResponse:
- """``client.session.revert.with_raw_response``."""
- return SessionRevertResourceWithRawResponse(self._resource.revert)
-
- @cached_property
- def inbox(self) -> SessionInboxResourceWithRawResponse:
- """``client.session.inbox.with_raw_response``."""
- return SessionInboxResourceWithRawResponse(self._resource.inbox)
-
- @cached_property
- def instructions(self) -> SessionInstructionsResourceWithRawResponse:
- """``client.session.instructions.with_raw_response``."""
- return SessionInstructionsResourceWithRawResponse(self._resource.instructions)
-
- @cached_property
- def message(self) -> SessionMessageResourceWithRawResponse:
- """``client.session.message.with_raw_response``."""
- return SessionMessageResourceWithRawResponse(self._resource.message)
-
- @cached_property
- def form(self) -> SessionFormResourceWithRawResponse:
- """``client.session.form.with_raw_response``."""
- return SessionFormResourceWithRawResponse(self._resource.form)
-
-
-class SessionResourceWithStreamingResponse:
- """``client.session.with_streaming_response``."""
-
- def __init__(self, resource: SessionResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
- self.stats = to_streamed_response_wrapper(resource.stats)
- self.create = to_streamed_response_wrapper(resource.create)
- self.import_ = to_streamed_response_wrapper(resource.import_)
- self.export = to_streamed_response_wrapper(resource.export)
- self.active = to_streamed_response_wrapper(resource.active)
- self.get = to_streamed_response_wrapper(resource.get)
- self.remove = to_streamed_response_wrapper(resource.remove)
- self.fork = to_streamed_response_wrapper(resource.fork)
- self.switch_agent = to_streamed_response_wrapper(resource.switch_agent)
- self.switch_model = to_streamed_response_wrapper(resource.switch_model)
- self.update = to_streamed_response_wrapper(resource.update)
- self.move = to_streamed_response_wrapper(resource.move)
- self.prompt = to_streamed_response_wrapper(resource.prompt)
- self.command = to_streamed_response_wrapper(resource.command)
- self.skill = to_streamed_response_wrapper(resource.skill)
- self.synthetic = to_streamed_response_wrapper(resource.synthetic)
- self.shell = to_streamed_response_wrapper(resource.shell)
- self.compact = to_streamed_response_wrapper(resource.compact)
- self.wait = to_streamed_response_wrapper(resource.wait)
- self.context = to_streamed_response_wrapper(resource.context)
- self.diff = to_streamed_response_wrapper(resource.diff)
- self.generate = to_streamed_response_wrapper(resource.generate)
- self.log = to_streamed_response_wrapper(resource.log)
- self.interrupt = to_streamed_response_wrapper(resource.interrupt)
- self.background = to_streamed_response_wrapper(resource.background)
- self.environment = to_streamed_response_wrapper(resource.environment)
- self.view = to_streamed_response_wrapper(resource.view)
-
- @cached_property
- def revert(self) -> SessionRevertResourceWithStreamingResponse:
- """``client.session.revert.with_streaming_response``."""
- return SessionRevertResourceWithStreamingResponse(self._resource.revert)
-
- @cached_property
- def inbox(self) -> SessionInboxResourceWithStreamingResponse:
- """``client.session.inbox.with_streaming_response``."""
- return SessionInboxResourceWithStreamingResponse(self._resource.inbox)
-
- @cached_property
- def instructions(self) -> SessionInstructionsResourceWithStreamingResponse:
- """``client.session.instructions.with_streaming_response``."""
- return SessionInstructionsResourceWithStreamingResponse(self._resource.instructions)
-
- @cached_property
- def message(self) -> SessionMessageResourceWithStreamingResponse:
- """``client.session.message.with_streaming_response``."""
- return SessionMessageResourceWithStreamingResponse(self._resource.message)
-
- @cached_property
- def form(self) -> SessionFormResourceWithStreamingResponse:
- """``client.session.form.with_streaming_response``."""
- return SessionFormResourceWithStreamingResponse(self._resource.form)
-
-
-class AsyncSessionRevertResource(AsyncAPIResource):
- """Operations under ``client.session.revert`` (OpenCode ``session.revert``)."""
-
- @cached_property
- def with_raw_response(self) -> AsyncSessionRevertResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncSessionRevertResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncSessionRevertResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncSessionRevertResourceWithStreamingResponse(self)
-
- async def stage(
- self,
- session_id: SessionID,
- *,
- message_id: SessionMessageID,
- files: bool | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionRevert:
- """Stage session revert.
-
- Stage or move a reversible session boundary and optionally apply its file changes.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- MessageNotFoundError: The server answered 404.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- SessionBusyError: The server answered 409.
- UnknownError: The server answered 500.
-
- ``POST /api/session/:sessionID/revert/stage``
- """
- return cast(
- "SessionRevert",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/revert/stage",
- body={"messageID": message_id, "files": files},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionRevert",
- unwrap=True,
- ),
- )
-
- async def clear(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Clear staged revert.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- SessionBusyError: The server answered 409.
- UnknownError: The server answered 500.
-
- ``DELETE /api/session/:sessionID/revert``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/session/{encode_path_segment(session_id)}/revert",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def commit(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Commit staged revert.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- SessionBusyError: The server answered 409.
-
- ``POST /api/session/:sessionID/revert/commit``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/revert/commit",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class AsyncSessionRevertResourceWithRawResponse:
- """``client.session.revert.with_raw_response``."""
-
- def __init__(self, resource: AsyncSessionRevertResource) -> None:
- self._resource = resource
- self.stage = async_to_raw_response_wrapper(resource.stage)
- self.clear = async_to_raw_response_wrapper(resource.clear)
- self.commit = async_to_raw_response_wrapper(resource.commit)
-
-
-class AsyncSessionRevertResourceWithStreamingResponse:
- """``client.session.revert.with_streaming_response``."""
-
- def __init__(self, resource: AsyncSessionRevertResource) -> None:
- self._resource = resource
- self.stage = async_to_streamed_response_wrapper(resource.stage)
- self.clear = async_to_streamed_response_wrapper(resource.clear)
- self.commit = async_to_streamed_response_wrapper(resource.commit)
-
-
-class AsyncSessionInboxResource(AsyncAPIResource):
- """Operations under ``client.session.inbox`` (OpenCode ``session.inbox``)."""
-
- @cached_property
- def with_raw_response(self) -> AsyncSessionInboxResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncSessionInboxResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncSessionInboxResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncSessionInboxResourceWithStreamingResponse(self)
-
- async def list(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[SessionInboxInfo]:
- """List session inbox.
-
- List durable enqueued session work not yet delivered, ordered by enqueue sequence. Includes user, synthetic, compaction, and move items.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
-
- ``GET /api/session/:sessionID/inbox``
- """
- return cast(
- "builtins.list[SessionInboxInfo]",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}/inbox",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[SessionInboxInfo]",
- unwrap=True,
- ),
- )
-
- async def cancel(
- self,
- session_id: SessionID,
- inbox_id: SessionMessageID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Cancel inbox input.
-
- Cancel an inbox item that has not yet been delivered. Unavailable items are a no-op.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
-
- ``DELETE /api/session/:sessionID/inbox/:inboxID``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/session/{encode_path_segment(session_id)}/inbox/{encode_path_segment(inbox_id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def update(
- self,
- session_id: SessionID,
- inbox_id: SessionMessageID,
- *,
- delivery: SessionInboxDelivery,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Update inbox item.
-
- Change a pending inbox item's delivery mode. Steering wakes session execution.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- ConflictError: The server answered 409.
-
- ``PATCH /api/session/:sessionID/inbox/:inboxID``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="PATCH",
- path=f"/api/session/{encode_path_segment(session_id)}/inbox/{encode_path_segment(inbox_id)}",
- body={"delivery": delivery},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class AsyncSessionInboxResourceWithRawResponse:
- """``client.session.inbox.with_raw_response``."""
-
- def __init__(self, resource: AsyncSessionInboxResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
- self.cancel = async_to_raw_response_wrapper(resource.cancel)
- self.update = async_to_raw_response_wrapper(resource.update)
-
-
-class AsyncSessionInboxResourceWithStreamingResponse:
- """``client.session.inbox.with_streaming_response``."""
-
- def __init__(self, resource: AsyncSessionInboxResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.cancel = async_to_streamed_response_wrapper(resource.cancel)
- self.update = async_to_streamed_response_wrapper(resource.update)
-
-
-class AsyncSessionInstructionsEntryResource(AsyncAPIResource):
- """Operations under ``client.session.instructions.entry`` (OpenCode ``session.instructions.entry``)."""
-
- @cached_property
- def with_raw_response(self) -> AsyncSessionInstructionsEntryResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncSessionInstructionsEntryResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncSessionInstructionsEntryResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncSessionInstructionsEntryResourceWithStreamingResponse(self)
-
- async def list(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[InstructionEntryInfo]:
- """List instruction entries.
-
- List API-managed instruction entries attached to the session.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/experimental/session/:sessionID/instructions/entries``
- """
- return cast(
- "builtins.list[InstructionEntryInfo]",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/instructions/entries",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[InstructionEntryInfo]",
- unwrap=True,
- ),
- )
-
- async def put(
- self,
- session_id: SessionID,
- key: InstructionEntryKey,
- *,
- value: Any,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Put instruction entry.
-
- Attach or replace one durable instruction entry. Changes announce as updates at the next step boundary.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- InstructionEntryValueTooLargeError: The server answered 413.
-
- ``PUT /api/experimental/session/:sessionID/instructions/entries/:key``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="PUT",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/instructions/entries/{encode_path_segment(key)}",
- body={"value": value},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def remove(
- self,
- session_id: SessionID,
- key: InstructionEntryKey,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Remove instruction entry.
-
- Remove one instruction entry; the removal is announced to the model at the next step boundary.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``DELETE /api/experimental/session/:sessionID/instructions/entries/:key``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/instructions/entries/{encode_path_segment(key)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class AsyncSessionInstructionsEntryResourceWithRawResponse:
- """``client.session.instructions.entry.with_raw_response``."""
-
- def __init__(self, resource: AsyncSessionInstructionsEntryResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
- self.put = async_to_raw_response_wrapper(resource.put)
- self.remove = async_to_raw_response_wrapper(resource.remove)
-
-
-class AsyncSessionInstructionsEntryResourceWithStreamingResponse:
- """``client.session.instructions.entry.with_streaming_response``."""
-
- def __init__(self, resource: AsyncSessionInstructionsEntryResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.put = async_to_streamed_response_wrapper(resource.put)
- self.remove = async_to_streamed_response_wrapper(resource.remove)
-
-
-class AsyncSessionInstructionsResource(AsyncAPIResource):
- """Operations under ``client.session.instructions`` (OpenCode ``session.instructions``)."""
-
- @cached_property
- def entry(self) -> AsyncSessionInstructionsEntryResource:
- """Operations under ``client.session.instructions.entry``."""
- return AsyncSessionInstructionsEntryResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> AsyncSessionInstructionsResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncSessionInstructionsResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncSessionInstructionsResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncSessionInstructionsResourceWithStreamingResponse(self)
-
-
-class AsyncSessionInstructionsResourceWithRawResponse:
- """``client.session.instructions.with_raw_response``."""
-
- def __init__(self, resource: AsyncSessionInstructionsResource) -> None:
- self._resource = resource
-
- @cached_property
- def entry(self) -> AsyncSessionInstructionsEntryResourceWithRawResponse:
- """``client.session.instructions.entry.with_raw_response``."""
- return AsyncSessionInstructionsEntryResourceWithRawResponse(self._resource.entry)
-
-
-class AsyncSessionInstructionsResourceWithStreamingResponse:
- """``client.session.instructions.with_streaming_response``."""
-
- def __init__(self, resource: AsyncSessionInstructionsResource) -> None:
- self._resource = resource
-
- @cached_property
- def entry(self) -> AsyncSessionInstructionsEntryResourceWithStreamingResponse:
- """``client.session.instructions.entry.with_streaming_response``."""
- return AsyncSessionInstructionsEntryResourceWithStreamingResponse(self._resource.entry)
-
-
-class AsyncSessionMessageResource(AsyncAPIResource):
- """Operations under ``client.session.message`` (OpenCode ``session.message``)."""
-
- @cached_property
- def with_raw_response(self) -> AsyncSessionMessageResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncSessionMessageResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncSessionMessageResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncSessionMessageResourceWithStreamingResponse(self)
-
- async def get(
- self,
- session_id: SessionID,
- message_id: SessionMessageID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionMessageInfo:
- """Get session message.
-
- Retrieve one projected message owned by the Session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- MessageNotFoundError: The server answered 404.
-
- ``GET /api/session/:sessionID/message/:messageID``
- """
- return cast(
- "SessionMessageInfo",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}/message/{encode_path_segment(message_id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionMessageInfo",
- unwrap=True,
- ),
- )
-
-
-class AsyncSessionMessageResourceWithRawResponse:
- """``client.session.message.with_raw_response``."""
-
- def __init__(self, resource: AsyncSessionMessageResource) -> None:
- self._resource = resource
- self.get = async_to_raw_response_wrapper(resource.get)
-
-
-class AsyncSessionMessageResourceWithStreamingResponse:
- """``client.session.message.with_streaming_response``."""
-
- def __init__(self, resource: AsyncSessionMessageResource) -> None:
- self._resource = resource
- self.get = async_to_streamed_response_wrapper(resource.get)
-
-
-class AsyncSessionFormResource(AsyncAPIResource):
- """Operations under ``client.session.form`` (OpenCode ``session.form``)."""
-
- @cached_property
- def with_raw_response(self) -> AsyncSessionFormResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncSessionFormResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncSessionFormResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncSessionFormResourceWithStreamingResponse(self)
-
- async def list(
- self,
- session_id: str,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[FormInfo]:
- """List session forms.
-
- Retrieve pending forms for a session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/session/:sessionID/form``
- """
- return cast(
- "builtins.list[FormInfo]",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}/form",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[FormInfo]",
- unwrap=True,
- ),
- )
-
- async def create(
- self,
- session_id: str,
- *,
- title: str,
- fields: FormFields,
- id: FormID | NotGiven | None = NOT_GIVEN,
- metadata: FormMetadata | NotGiven = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> FormInfo:
- """Create session form.
-
- Create a form for a session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- ConflictError: The server answered 409.
-
- ``POST /api/session/:sessionID/form``
- """
- return cast(
- "FormInfo",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/form",
- body={"id": id, "title": title, "metadata": metadata, "fields": fields},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="FormInfo",
- unwrap=True,
- ),
- )
-
- async def get(
- self,
- session_id: str,
- form_id: FormID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> FormDetail:
- """Get session form.
-
- Retrieve a form and its current state for a session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- FormNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/session/:sessionID/form/:formID``
- """
- return cast(
- "FormDetail",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}/form/{encode_path_segment(form_id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="FormDetail",
- unwrap=True,
- ),
- )
-
- async def reply(
- self,
- session_id: str,
- form_id: FormID,
- *,
- answer: FormAnswer,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Reply to form.
-
- Submit an answer to a pending form.
-
- Raises:
- FormInvalidAnswerError: The server answered 400.
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- FormNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- FormAlreadySettledError: The server answered 409.
-
- ``POST /api/session/:sessionID/form/:formID/reply``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/form/{encode_path_segment(form_id)}/reply",
- body={"answer": answer},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def cancel(
- self,
- session_id: str,
- form_id: FormID,
- *,
- message: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Cancel form.
-
- Cancel a pending form, optionally telling the asker why it was not answered.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- FormNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- FormAlreadySettledError: The server answered 409.
-
- ``DELETE /api/session/:sessionID/form/:formID``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/session/{encode_path_segment(session_id)}/form/{encode_path_segment(form_id)}",
- query={"message": message},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class AsyncSessionFormResourceWithRawResponse:
- """``client.session.form.with_raw_response``."""
-
- def __init__(self, resource: AsyncSessionFormResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
- self.create = async_to_raw_response_wrapper(resource.create)
- self.get = async_to_raw_response_wrapper(resource.get)
- self.reply = async_to_raw_response_wrapper(resource.reply)
- self.cancel = async_to_raw_response_wrapper(resource.cancel)
-
-
-class AsyncSessionFormResourceWithStreamingResponse:
- """``client.session.form.with_streaming_response``."""
-
- def __init__(self, resource: AsyncSessionFormResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.create = async_to_streamed_response_wrapper(resource.create)
- self.get = async_to_streamed_response_wrapper(resource.get)
- self.reply = async_to_streamed_response_wrapper(resource.reply)
- self.cancel = async_to_streamed_response_wrapper(resource.cancel)
-
-
-class AsyncSessionResource(AsyncSessionHelpers):
- """Operations under ``client.session`` (OpenCode ``session``)."""
-
- @cached_property
- def revert(self) -> AsyncSessionRevertResource:
- """Operations under ``client.session.revert``."""
- return AsyncSessionRevertResource(self._client)
-
- @cached_property
- def inbox(self) -> AsyncSessionInboxResource:
- """Operations under ``client.session.inbox``."""
- return AsyncSessionInboxResource(self._client)
-
- @cached_property
- def instructions(self) -> AsyncSessionInstructionsResource:
- """Operations under ``client.session.instructions``."""
- return AsyncSessionInstructionsResource(self._client)
-
- @cached_property
- def message(self) -> AsyncSessionMessageResource:
- """Operations under ``client.session.message``."""
- return AsyncSessionMessageResource(self._client)
-
- @cached_property
- def form(self) -> AsyncSessionFormResource:
- """Operations under ``client.session.form``."""
- return AsyncSessionFormResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> AsyncSessionResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncSessionResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncSessionResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncSessionResourceWithStreamingResponse(self)
-
- def list(
- self,
- *,
- limit: int | NotGiven | None = NOT_GIVEN,
- order: OpenEnum[Literal["asc", "desc"]] | NotGiven | None = NOT_GIVEN,
- search: str | NotGiven | None = NOT_GIVEN,
- parent_id: SessionID | NotGiven | None = NOT_GIVEN,
- archived: bool | NotGiven | None = NOT_GIVEN,
- directory: AbsolutePath | NotGiven | None = NOT_GIVEN,
- project: ProjectID | NotGiven | None = NOT_GIVEN,
- subpath: RelativePath | NotGiven | None = NOT_GIVEN,
- cursor: SessionsCursor | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> AsyncPaginator[SessionInfo]:
- """List sessions.
-
- Retrieve sessions in the requested order. Items keep that order across pages; use cursor.next or cursor.previous to move through the ordered list.
-
- Args:
- limit: Maximum number of sessions to return. Defaults to the newest 50 sessions.
- order: Session order for the first page. Use desc for newest first or asc for oldest first.
- parent_id: Filter by parent session. Use null to return only root sessions.
- archived: Filter by archive state. Use true to return only archived sessions or false to exclude them. Omit to include both.
-
- Raises:
- InvalidCursorError: The server answered 400.
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
-
- ``GET /api/session``
- """
- return self._client.page(
- RequestOptions(
- method="GET",
- path="/api/session",
- query={
- "limit": limit,
- "order": order,
- "search": search,
- "parentID": parent_id,
- "archived": archived,
- "directory": directory,
- "project": project,
- "subpath": subpath,
- "cursor": cursor,
- },
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionsResponse",
- )
-
- async def stats(
- self,
- *,
- from_: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]] | NotGiven | None = NOT_GIVEN,
- to: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]] | NotGiven | None = NOT_GIVEN,
- project: ProjectID | NotGiven | None = NOT_GIVEN,
- timezone: str | NotGiven | None = NOT_GIVEN,
- tools: OpenEnum[Literal["none", "summary", "detail"]] | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionStatsInfo:
- """Get session statistics.
-
- Aggregate local session activity, usage, and tool reliability for a time range.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
-
- ``GET /api/experimental/session/stats``
- """
- return cast(
- "SessionStatsInfo",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/experimental/session/stats",
- query={"from": from_, "to": to, "project": project, "timezone": timezone, "tools": tools},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionStatsInfo",
- unwrap=True,
- ),
- )
-
- async def create(
- self,
- *,
- id: SessionID | NotGiven | None = NOT_GIVEN,
- parent_id: SessionID | NotGiven | None = NOT_GIVEN,
- title: str | NotGiven | None = NOT_GIVEN,
- agent: AgentID | NotGiven | None = NOT_GIVEN,
- model: ModelRef | NotGiven | None = NOT_GIVEN,
- location: LocationPublicRef | NotGiven | None = NOT_GIVEN,
- metadata: SessionMetadata | NotGiven | None = NOT_GIVEN,
- permissions: PermissionRuleset | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionInfo:
- """Create session.
-
- Create a session at the requested location. A parentID creates a linked child session at its parent's location.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
-
- ``POST /api/session``
- """
- return cast(
- "SessionInfo",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/session",
- body={
- "id": id,
- "parentID": parent_id,
- "title": title,
- "agent": agent,
- "model": model,
- "location": self._location(location),
- "metadata": metadata,
- "permissions": permissions,
- },
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionInfo",
- unwrap=True,
- ),
- )
-
- async def import_(
- self,
- *,
- info: SessionInfo,
- messages: Sequence[Any],
- location: LocationPublicRef | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionInfo:
- """Import session.
-
- Import a projected session transcript at the requested location. Accepts transcripts exported by earlier releases. If parentID is supplied, the parent session must already exist; import parents before children.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- ConflictError: The server answered 409.
-
- ``POST /api/experimental/session/import``
- """
- return cast(
- "SessionInfo",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/experimental/session/import",
- body={"info": info, "messages": messages, "location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionInfo",
- unwrap=True,
- ),
- )
-
- async def export(
- self,
- session_id: SessionID,
- *,
- sanitize: bool | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionTransferData:
- """Export session.
-
- Export a complete projected session transcript.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- UnknownError: The server answered 500.
-
- ``GET /api/experimental/session/:sessionID/export``
- """
- return cast(
- "SessionTransferData",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/export",
- query={"sanitize": sanitize},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionTransferData",
- unwrap=True,
- ),
- )
-
- async def active(
- self,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.dict[str, Any]:
- """List active sessions.
-
- Retrieve foreground Session drains currently owned by this OpenCode process. Sessions absent from the result are inactive.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
-
- ``GET /api/session/active``
- """
- return cast(
- "builtins.dict[str, Any]",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/session/active",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.dict[str, Any]",
- unwrap=True,
- ),
- )
-
- async def get(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionInfo:
- """Get session.
-
- Retrieve a session by ID.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
-
- ``GET /api/session/:sessionID``
- """
- return cast(
- "SessionInfo",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionInfo",
- unwrap=True,
- ),
- )
-
- async def remove(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Delete session.
-
- Delete a session and its child sessions.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
-
- ``DELETE /api/session/:sessionID``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="DELETE",
- path=f"/api/session/{encode_path_segment(session_id)}",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def fork(
- self,
- session_id: SessionID,
- *,
- before: SessionMessageID | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionInfo:
- """Fork session.
-
- Create a child session by copying projected history before a message. Omit before to copy the full history.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- MessageNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/fork``
- """
- return cast(
- "SessionInfo",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/fork",
- body={"before": before},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionInfo",
- unwrap=True,
- ),
- )
-
- async def switch_agent(
- self,
- session_id: SessionID,
- *,
- agent: AgentID,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Switch session agent.
-
- Switch the agent used by subsequent provider turns.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/agent``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/agent",
- body={"agent": agent},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def switch_model(
- self,
- session_id: SessionID,
- *,
- model: ModelRef,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Switch session model.
-
- Switch the model used by subsequent provider turns.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/model``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/model",
- body={"model": model},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def update(
- self,
- session_id: SessionID,
- *,
- title: str | NotGiven | None = NOT_GIVEN,
- metadata: SessionMetadata | NotGiven | None = NOT_GIVEN,
- permissions: PermissionRuleset | NotGiven | None = NOT_GIVEN,
- archived: bool | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Update session.
-
- Update mutable session properties.
-
- Args:
- archived: Archive the session with true or restore it with false. Repeating the current state is a no-op.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``PATCH /api/session/:sessionID``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="PATCH",
- path=f"/api/session/{encode_path_segment(session_id)}",
- body={"title": title, "metadata": metadata, "permissions": permissions, "archived": archived},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def move(
- self,
- session_id: SessionID,
- *,
- directory: AbsolutePath,
- delivery: SessionInboxDelivery | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Move session.
-
- Move a session to another project directory at the requested delivery boundary.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/move``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/move",
- body={"directory": directory, "delivery": delivery},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def prompt(
- self,
- session_id: SessionID,
- *,
- text: str,
- id: SessionMessageID | NotGiven | None = NOT_GIVEN,
- files: Sequence[PromptInputFileAttachment] | NotGiven = NOT_GIVEN,
- agents: Sequence[PromptAgentAttachment] | NotGiven = NOT_GIVEN,
- skills: Sequence[PromptInputSkillAttachment] | NotGiven = NOT_GIVEN,
- metadata: Mapping[str, Any] | NotGiven = NOT_GIVEN,
- delivery: SessionInboxDelivery | NotGiven | None = NOT_GIVEN,
- resume: bool | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionInboxUser:
- """Send message.
-
- Durably admit one session input and schedule agent-loop execution unless resume is false.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- ConflictError: The server answered 409.
-
- ``POST /api/session/:sessionID/prompt``
- """
- return cast(
- "SessionInboxUser",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/prompt",
- body={
- "id": id,
- "text": text,
- "files": files,
- "agents": agents,
- "skills": skills,
- "metadata": metadata,
- "delivery": delivery,
- "resume": resume,
- },
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionInboxUser",
- unwrap=True,
- ),
- )
-
- async def command(
- self,
- session_id: SessionID,
- *,
- name: str,
- text: str,
- files: Sequence[PromptInputFileAttachment] | NotGiven = NOT_GIVEN,
- agents: Sequence[PromptAgentAttachment] | NotGiven = NOT_GIVEN,
- skills: Sequence[PromptInputSkillAttachment] | NotGiven = NOT_GIVEN,
- delivery: SessionInboxDelivery | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Run command.
-
- Execute a slash command callback immediately.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- CommandNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- CommandExecutionError: The server answered 500.
-
- ``POST /api/session/:sessionID/command``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/command",
- body={
- "name": name,
- "text": text,
- "files": files,
- "agents": agents,
- "skills": skills,
- "delivery": delivery,
- },
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def skill(
- self,
- session_id: SessionID,
- *,
- id: SkillID,
- resume: bool | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Activate skill.
-
- Activate a skill for a session by appending a skill message and resuming execution.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- SkillNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/experimental/session/:sessionID/skill``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/skill",
- body={"id": id, "resume": resume},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def synthetic(
- self,
- session_id: SessionID,
- *,
- text: str,
- id: SessionMessageID | NotGiven | None = NOT_GIVEN,
- description: str | NotGiven | None = NOT_GIVEN,
- metadata: Mapping[str, Any] | NotGiven = NOT_GIVEN,
- delivery: SessionInboxDelivery | NotGiven | None = NOT_GIVEN,
- resume: bool | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionInboxSynthetic:
- """Add synthetic message.
-
- Durably admit synthetic session input and schedule execution unless resume is false.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- ConflictError: The server answered 409.
-
- ``POST /api/session/:sessionID/synthetic``
- """
- return cast(
- "SessionInboxSynthetic",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/synthetic",
- body={
- "id": id,
- "text": text,
- "description": description,
- "metadata": metadata,
- "delivery": delivery,
- "resume": resume,
- },
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionInboxSynthetic",
- unwrap=True,
- ),
- )
-
- async def shell(
- self,
- session_id: SessionID,
- *,
- command: str,
- id: SessionMessageID | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Run shell command.
-
- Execute one shell command in the session's working directory. Emits a shell.started event before execution and a shell.ended event with the merged output after.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/shell``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/shell",
- body={"id": id, "command": command},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def compact(
- self,
- session_id: SessionID,
- *,
- id: SessionMessageID | NotGiven | None = NOT_GIVEN,
- delivery: SessionInboxDelivery | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionInboxCompaction:
- """Compact session.
-
- Durably admit a session compaction request. Steers by default: it runs at the next step boundary instead of waiting behind queued prompts.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- ConflictError: The server answered 409.
-
- ``POST /api/session/:sessionID/compact``
- """
- return cast(
- "SessionInboxCompaction",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/compact",
- body={"id": id, "delivery": delivery},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionInboxCompaction",
- unwrap=True,
- ),
- )
-
- async def wait(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Wait for session.
-
- Wait for a session agent loop to become idle.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``POST /api/experimental/session/:sessionID/wait``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/wait",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def context(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[SessionMessageInfo]:
- """Get session context.
-
- Retrieve the active context messages for a session (all messages after the last compaction).
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- UnknownError: The server answered 500.
-
- ``GET /api/session/:sessionID/context``
- """
- return cast(
- "builtins.list[SessionMessageInfo]",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}/context",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[SessionMessageInfo]",
- unwrap=True,
- ),
- )
-
- async def diff(
- self,
- session_id: SessionID,
- *,
- from_: SessionMessageID | NotGiven | None = NOT_GIVEN,
- to: SessionMessageID | NotGiven | None = NOT_GIVEN,
- context: int | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> builtins.list[FileDiffInfo]:
- """Diff session turns.
-
- Structured per-file diffs of the files a turn changed. A turn runs from the first prompt after the session was last idle until its next idle marker, so prompts steered in while it was busy belong to the same turn; `to` extends the range through a later turn. Compares the range's first recorded snapshot with its last; a step still running in the active session compares against the working copy. Ranges that span a location change are rejected. In sessions without any idle marker, a prompt's turn spans until the next user message.
-
- Args:
- from_: User message whose turn to diff. Defaults to the turn of the newest user message.
- to: Later user message whose turn ends the range. Defaults to the turn of `from` alone.
- context: Unchanged lines around each hunk. Omit for full-file patches.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- MessageNotFoundError: The server answered 404.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- UnknownError: The server answered 500.
-
- ``GET /api/session/:sessionID/diff``
- """
- return cast(
- "builtins.list[FileDiffInfo]",
- await self._client.request(
- RequestOptions(
- method="GET",
- path=f"/api/session/{encode_path_segment(session_id)}/diff",
- query={"from": from_, "to": to, "context": context},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="builtins.list[FileDiffInfo]",
- unwrap=True,
- ),
- )
-
- async def generate(
- self,
- session_id: SessionID,
- *,
- prompt: str,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionGenerateResponseData:
- """Generate text from session context.
-
- Generate transient text from the current session context without mutating session history.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``POST /api/session/:sessionID/generate``
- """
- return cast(
- "SessionGenerateResponseData",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/generate",
- body={"prompt": prompt},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionGenerateResponseData",
- unwrap=True,
- ),
- )
-
- async def log(
- self,
- session_id: SessionID,
- *,
- after: EventSeq | NotGiven | None = NOT_GIVEN,
- follow: bool | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> AsyncStream[SessionLogItem]:
- """Read the session log.
-
- Experimental durable session event log. Reads events after an exclusive aggregate sequence and continues with live events when follow=true.
-
- This endpoint is experimental in OpenCode and may change without notice.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
-
- ``GET /api/experimental/session/:sessionID/log``
- """
- return await self._client.stream(
- RequestOptions(
- method="GET",
- path=f"/api/experimental/session/{encode_path_segment(session_id)}/log",
- query={"after": after, "follow": follow},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- item_type="SessionLogItem",
- )
-
- async def interrupt(
- self,
- session_id: SessionID,
- *,
- resume: bool | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> SessionInterruptResponse:
- """Interrupt session execution.
-
- Interrupt active execution owned by this OpenCode process. Returns interrupted=true when an active execution was interrupted and false for the idle no-op. When resume=true, execution resumes pending steering input and next-in-line control items (manual compaction, moves) while queued prompts remain parked.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/interrupt``
- """
- return cast(
- "SessionInterruptResponse",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/interrupt",
- query={"resume": resume},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="SessionInterruptResponse",
- ),
- )
-
- async def background(
- self,
- session_id: SessionID,
- *,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Background blocking session tools.
-
- Move active foreground backgroundable tools for this session into background observation. Idle requests are a no-op.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
- LocationNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/background``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/background",
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def environment(
- self,
- session_id: SessionID,
- *,
- variables: Mapping[str, str],
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Set session environment.
-
- Replace the process environment used by local shell commands for this session.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
-
- ``PUT /api/session/:sessionID/environment``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="PUT",
- path=f"/api/session/{encode_path_segment(session_id)}/environment",
- body={"variables": variables},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def view(
- self,
- session_id: SessionID,
- *,
- idle: float,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """View session.
-
- Mark the idle transition observed by the viewer as viewed.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- SessionNotFoundError: The server answered 404.
-
- ``POST /api/session/:sessionID/view``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path=f"/api/session/{encode_path_segment(session_id)}/view",
- body={"idle": idle},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
-
-class AsyncSessionResourceWithRawResponse:
- """``client.session.with_raw_response``."""
-
- def __init__(self, resource: AsyncSessionResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
- self.stats = async_to_raw_response_wrapper(resource.stats)
- self.create = async_to_raw_response_wrapper(resource.create)
- self.import_ = async_to_raw_response_wrapper(resource.import_)
- self.export = async_to_raw_response_wrapper(resource.export)
- self.active = async_to_raw_response_wrapper(resource.active)
- self.get = async_to_raw_response_wrapper(resource.get)
- self.remove = async_to_raw_response_wrapper(resource.remove)
- self.fork = async_to_raw_response_wrapper(resource.fork)
- self.switch_agent = async_to_raw_response_wrapper(resource.switch_agent)
- self.switch_model = async_to_raw_response_wrapper(resource.switch_model)
- self.update = async_to_raw_response_wrapper(resource.update)
- self.move = async_to_raw_response_wrapper(resource.move)
- self.prompt = async_to_raw_response_wrapper(resource.prompt)
- self.command = async_to_raw_response_wrapper(resource.command)
- self.skill = async_to_raw_response_wrapper(resource.skill)
- self.synthetic = async_to_raw_response_wrapper(resource.synthetic)
- self.shell = async_to_raw_response_wrapper(resource.shell)
- self.compact = async_to_raw_response_wrapper(resource.compact)
- self.wait = async_to_raw_response_wrapper(resource.wait)
- self.context = async_to_raw_response_wrapper(resource.context)
- self.diff = async_to_raw_response_wrapper(resource.diff)
- self.generate = async_to_raw_response_wrapper(resource.generate)
- self.log = async_to_raw_response_wrapper(resource.log)
- self.interrupt = async_to_raw_response_wrapper(resource.interrupt)
- self.background = async_to_raw_response_wrapper(resource.background)
- self.environment = async_to_raw_response_wrapper(resource.environment)
- self.view = async_to_raw_response_wrapper(resource.view)
-
- @cached_property
- def revert(self) -> AsyncSessionRevertResourceWithRawResponse:
- """``client.session.revert.with_raw_response``."""
- return AsyncSessionRevertResourceWithRawResponse(self._resource.revert)
-
- @cached_property
- def inbox(self) -> AsyncSessionInboxResourceWithRawResponse:
- """``client.session.inbox.with_raw_response``."""
- return AsyncSessionInboxResourceWithRawResponse(self._resource.inbox)
-
- @cached_property
- def instructions(self) -> AsyncSessionInstructionsResourceWithRawResponse:
- """``client.session.instructions.with_raw_response``."""
- return AsyncSessionInstructionsResourceWithRawResponse(self._resource.instructions)
-
- @cached_property
- def message(self) -> AsyncSessionMessageResourceWithRawResponse:
- """``client.session.message.with_raw_response``."""
- return AsyncSessionMessageResourceWithRawResponse(self._resource.message)
-
- @cached_property
- def form(self) -> AsyncSessionFormResourceWithRawResponse:
- """``client.session.form.with_raw_response``."""
- return AsyncSessionFormResourceWithRawResponse(self._resource.form)
-
-
-class AsyncSessionResourceWithStreamingResponse:
- """``client.session.with_streaming_response``."""
-
- def __init__(self, resource: AsyncSessionResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
- self.stats = async_to_streamed_response_wrapper(resource.stats)
- self.create = async_to_streamed_response_wrapper(resource.create)
- self.import_ = async_to_streamed_response_wrapper(resource.import_)
- self.export = async_to_streamed_response_wrapper(resource.export)
- self.active = async_to_streamed_response_wrapper(resource.active)
- self.get = async_to_streamed_response_wrapper(resource.get)
- self.remove = async_to_streamed_response_wrapper(resource.remove)
- self.fork = async_to_streamed_response_wrapper(resource.fork)
- self.switch_agent = async_to_streamed_response_wrapper(resource.switch_agent)
- self.switch_model = async_to_streamed_response_wrapper(resource.switch_model)
- self.update = async_to_streamed_response_wrapper(resource.update)
- self.move = async_to_streamed_response_wrapper(resource.move)
- self.prompt = async_to_streamed_response_wrapper(resource.prompt)
- self.command = async_to_streamed_response_wrapper(resource.command)
- self.skill = async_to_streamed_response_wrapper(resource.skill)
- self.synthetic = async_to_streamed_response_wrapper(resource.synthetic)
- self.shell = async_to_streamed_response_wrapper(resource.shell)
- self.compact = async_to_streamed_response_wrapper(resource.compact)
- self.wait = async_to_streamed_response_wrapper(resource.wait)
- self.context = async_to_streamed_response_wrapper(resource.context)
- self.diff = async_to_streamed_response_wrapper(resource.diff)
- self.generate = async_to_streamed_response_wrapper(resource.generate)
- self.log = async_to_streamed_response_wrapper(resource.log)
- self.interrupt = async_to_streamed_response_wrapper(resource.interrupt)
- self.background = async_to_streamed_response_wrapper(resource.background)
- self.environment = async_to_streamed_response_wrapper(resource.environment)
- self.view = async_to_streamed_response_wrapper(resource.view)
-
- @cached_property
- def revert(self) -> AsyncSessionRevertResourceWithStreamingResponse:
- """``client.session.revert.with_streaming_response``."""
- return AsyncSessionRevertResourceWithStreamingResponse(self._resource.revert)
-
- @cached_property
- def inbox(self) -> AsyncSessionInboxResourceWithStreamingResponse:
- """``client.session.inbox.with_streaming_response``."""
- return AsyncSessionInboxResourceWithStreamingResponse(self._resource.inbox)
-
- @cached_property
- def instructions(self) -> AsyncSessionInstructionsResourceWithStreamingResponse:
- """``client.session.instructions.with_streaming_response``."""
- return AsyncSessionInstructionsResourceWithStreamingResponse(self._resource.instructions)
-
- @cached_property
- def message(self) -> AsyncSessionMessageResourceWithStreamingResponse:
- """``client.session.message.with_streaming_response``."""
- return AsyncSessionMessageResourceWithStreamingResponse(self._resource.message)
-
- @cached_property
- def form(self) -> AsyncSessionFormResourceWithStreamingResponse:
- """``client.session.form.with_streaming_response``."""
- return AsyncSessionFormResourceWithStreamingResponse(self._resource.form)
diff --git a/src/opencode/resources/vcs.py b/src/opencode/resources/vcs.py
deleted file mode 100644
index a913df1..0000000
--- a/src/opencode/resources/vcs.py
+++ /dev/null
@@ -1,652 +0,0 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""The ``vcs`` resource."""
-
-from __future__ import annotations
-
-from functools import cached_property
-from typing import TYPE_CHECKING, cast
-
-from .._base_client import RequestOptions
-from .._resource import AsyncAPIResource, SyncAPIResource
-from .._response import (
- async_to_raw_response_wrapper,
- async_to_streamed_response_wrapper,
- to_raw_response_wrapper,
- to_streamed_response_wrapper,
-)
-from .._types import NOT_GIVEN, Body, Headers, NotGiven, Query, Timeout
-
-if TYPE_CHECKING:
- from ..types import (
- Location,
- VcsBaseResponse,
- VcsBranchListResponse,
- VcsDiffResponse,
- VcsGetResponse,
- VcsMode,
- VcsStatusResponse,
- )
-
-__all__ = ["VcsResource", "AsyncVcsResource"]
-
-
-class VcsBranchResource(SyncAPIResource):
- """Operations under ``client.vcs.branch`` (OpenCode ``vcs.branch``)."""
-
- @cached_property
- def with_raw_response(self) -> VcsBranchResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return VcsBranchResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> VcsBranchResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return VcsBranchResourceWithStreamingResponse(self)
-
- def list(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- search: str | NotGiven | None = NOT_GIVEN,
- limit: int | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> VcsBranchListResponse:
- """VCS branches.
-
- List local and remote branches available at the requested location.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/vcs/branch``
- """
- return cast(
- "VcsBranchListResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/vcs/branch",
- query={"location": self._location(location), "search": search, "limit": limit},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="VcsBranchListResponse",
- ),
- )
-
-
-class VcsBranchResourceWithRawResponse:
- """``client.vcs.branch.with_raw_response``."""
-
- def __init__(self, resource: VcsBranchResource) -> None:
- self._resource = resource
- self.list = to_raw_response_wrapper(resource.list)
-
-
-class VcsBranchResourceWithStreamingResponse:
- """``client.vcs.branch.with_streaming_response``."""
-
- def __init__(self, resource: VcsBranchResource) -> None:
- self._resource = resource
- self.list = to_streamed_response_wrapper(resource.list)
-
-
-class VcsResource(SyncAPIResource):
- """Operations under ``client.vcs`` (OpenCode ``vcs``)."""
-
- @cached_property
- def branch(self) -> VcsBranchResource:
- """Operations under ``client.vcs.branch``."""
- return VcsBranchResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> VcsResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return VcsResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> VcsResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return VcsResourceWithStreamingResponse(self)
-
- def init(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- provider: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Initialize VCS repository.
-
- Initialize a repository using the selected VCS provider in a markerless project's directory and refresh its location services. Omitting provider defaults to git; built-in git and hg providers support initialization. An unknown provider returns 400; a registered provider without init returns 501.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ConflictError: The server answered 409.
- VcsInitNotSupportedError: The server answered 501.
- ServiceUnavailableError: The server answered 503.
-
- ``POST /api/vcs/init``
- """
- return cast(
- "None",
- self._client.request(
- RequestOptions(
- method="POST",
- path="/api/vcs/init",
- query={"location": self._location(location), "provider": provider},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- def get(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> VcsGetResponse:
- """VCS info.
-
- Get current and default branch information for the requested location.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/vcs``
- """
- return cast(
- "VcsGetResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/vcs",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="VcsGetResponse",
- ),
- )
-
- def base(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> VcsBaseResponse:
- """VCS review base.
-
- Infer a local review base from named branch creation history, or the repository default only when currently on that branch. Returns null before the first commit or when the provider lacks base metadata; ambiguous Git history requires an explicit base on diff requests.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/vcs/base``
- """
- return cast(
- "VcsBaseResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/vcs/base",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="VcsBaseResponse",
- ),
- )
-
- def status(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> VcsStatusResponse:
- """VCS status.
-
- List uncommitted working-copy changes relative to the requested location.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/vcs/status``
- """
- return cast(
- "VcsStatusResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/vcs/status",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="VcsStatusResponse",
- ),
- )
-
- def diff(
- self,
- *,
- mode: VcsMode,
- location: Location | NotGiven | None = NOT_GIVEN,
- base: str | NotGiven | None = NOT_GIVEN,
- context: int | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> VcsDiffResponse:
- """VCS diff.
-
- Diff HEAD to the working copy (working), the base merge-base to the working copy (branch), or the base merge-base to HEAD (committed). Omitting base preserves repository-default comparison; supplying it overrides the comparison without saving it.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/vcs/diff``
- """
- return cast(
- "VcsDiffResponse",
- self._client.request(
- RequestOptions(
- method="GET",
- path="/api/vcs/diff",
- query={"location": self._location(location), "mode": mode, "base": base, "context": context},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="VcsDiffResponse",
- ),
- )
-
-
-class VcsResourceWithRawResponse:
- """``client.vcs.with_raw_response``."""
-
- def __init__(self, resource: VcsResource) -> None:
- self._resource = resource
- self.init = to_raw_response_wrapper(resource.init)
- self.get = to_raw_response_wrapper(resource.get)
- self.base = to_raw_response_wrapper(resource.base)
- self.status = to_raw_response_wrapper(resource.status)
- self.diff = to_raw_response_wrapper(resource.diff)
-
- @cached_property
- def branch(self) -> VcsBranchResourceWithRawResponse:
- """``client.vcs.branch.with_raw_response``."""
- return VcsBranchResourceWithRawResponse(self._resource.branch)
-
-
-class VcsResourceWithStreamingResponse:
- """``client.vcs.with_streaming_response``."""
-
- def __init__(self, resource: VcsResource) -> None:
- self._resource = resource
- self.init = to_streamed_response_wrapper(resource.init)
- self.get = to_streamed_response_wrapper(resource.get)
- self.base = to_streamed_response_wrapper(resource.base)
- self.status = to_streamed_response_wrapper(resource.status)
- self.diff = to_streamed_response_wrapper(resource.diff)
-
- @cached_property
- def branch(self) -> VcsBranchResourceWithStreamingResponse:
- """``client.vcs.branch.with_streaming_response``."""
- return VcsBranchResourceWithStreamingResponse(self._resource.branch)
-
-
-class AsyncVcsBranchResource(AsyncAPIResource):
- """Operations under ``client.vcs.branch`` (OpenCode ``vcs.branch``)."""
-
- @cached_property
- def with_raw_response(self) -> AsyncVcsBranchResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncVcsBranchResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncVcsBranchResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncVcsBranchResourceWithStreamingResponse(self)
-
- async def list(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- search: str | NotGiven | None = NOT_GIVEN,
- limit: int | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> VcsBranchListResponse:
- """VCS branches.
-
- List local and remote branches available at the requested location.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/vcs/branch``
- """
- return cast(
- "VcsBranchListResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/vcs/branch",
- query={"location": self._location(location), "search": search, "limit": limit},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="VcsBranchListResponse",
- ),
- )
-
-
-class AsyncVcsBranchResourceWithRawResponse:
- """``client.vcs.branch.with_raw_response``."""
-
- def __init__(self, resource: AsyncVcsBranchResource) -> None:
- self._resource = resource
- self.list = async_to_raw_response_wrapper(resource.list)
-
-
-class AsyncVcsBranchResourceWithStreamingResponse:
- """``client.vcs.branch.with_streaming_response``."""
-
- def __init__(self, resource: AsyncVcsBranchResource) -> None:
- self._resource = resource
- self.list = async_to_streamed_response_wrapper(resource.list)
-
-
-class AsyncVcsResource(AsyncAPIResource):
- """Operations under ``client.vcs`` (OpenCode ``vcs``)."""
-
- @cached_property
- def branch(self) -> AsyncVcsBranchResource:
- """Operations under ``client.vcs.branch``."""
- return AsyncVcsBranchResource(self._client)
-
- @cached_property
- def with_raw_response(self) -> AsyncVcsResourceWithRawResponse:
- """Same methods, returning the raw HTTP response; call ``.parse()`` on it for the usual value."""
- return AsyncVcsResourceWithRawResponse(self)
-
- @cached_property
- def with_streaming_response(self) -> AsyncVcsResourceWithStreamingResponse:
- """Same methods, returning a context manager over the HTTP response without reading the body."""
- return AsyncVcsResourceWithStreamingResponse(self)
-
- async def init(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- provider: str | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> None:
- """Initialize VCS repository.
-
- Initialize a repository using the selected VCS provider in a markerless project's directory and refresh its location services. Omitting provider defaults to git; built-in git and hg providers support initialization. An unknown provider returns 400; a registered provider without init returns 501.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ConflictError: The server answered 409.
- VcsInitNotSupportedError: The server answered 501.
- ServiceUnavailableError: The server answered 503.
-
- ``POST /api/vcs/init``
- """
- return cast(
- "None",
- await self._client.request(
- RequestOptions(
- method="POST",
- path="/api/vcs/init",
- query={"location": self._location(location), "provider": provider},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- kind="void",
- ),
- )
-
- async def get(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> VcsGetResponse:
- """VCS info.
-
- Get current and default branch information for the requested location.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/vcs``
- """
- return cast(
- "VcsGetResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/vcs",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="VcsGetResponse",
- ),
- )
-
- async def base(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> VcsBaseResponse:
- """VCS review base.
-
- Infer a local review base from named branch creation history, or the repository default only when currently on that branch. Returns null before the first commit or when the provider lacks base metadata; ambiguous Git history requires an explicit base on diff requests.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/vcs/base``
- """
- return cast(
- "VcsBaseResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/vcs/base",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="VcsBaseResponse",
- ),
- )
-
- async def status(
- self,
- *,
- location: Location | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> VcsStatusResponse:
- """VCS status.
-
- List uncommitted working-copy changes relative to the requested location.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
-
- ``GET /api/vcs/status``
- """
- return cast(
- "VcsStatusResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/vcs/status",
- query={"location": self._location(location)},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="VcsStatusResponse",
- ),
- )
-
- async def diff(
- self,
- *,
- mode: VcsMode,
- location: Location | NotGiven | None = NOT_GIVEN,
- base: str | NotGiven | None = NOT_GIVEN,
- context: int | NotGiven | None = NOT_GIVEN,
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: Timeout | NotGiven = NOT_GIVEN,
- ) -> VcsDiffResponse:
- """VCS diff.
-
- Diff HEAD to the working copy (working), the base merge-base to the working copy (branch), or the base merge-base to HEAD (committed). Omitting base preserves repository-default comparison; supplying it overrides the comparison without saving it.
-
- Raises:
- InvalidRequestError: The server answered 400.
- UnauthorizedError: The server answered 401.
- LocationNotFoundError: The server answered 404.
- ServiceUnavailableError: The server answered 503.
-
- ``GET /api/vcs/diff``
- """
- return cast(
- "VcsDiffResponse",
- await self._client.request(
- RequestOptions(
- method="GET",
- path="/api/vcs/diff",
- query={"location": self._location(location), "mode": mode, "base": base, "context": context},
- extra_headers=extra_headers,
- extra_query=extra_query,
- extra_body=extra_body,
- timeout=timeout,
- ),
- response_type="VcsDiffResponse",
- ),
- )
-
-
-class AsyncVcsResourceWithRawResponse:
- """``client.vcs.with_raw_response``."""
-
- def __init__(self, resource: AsyncVcsResource) -> None:
- self._resource = resource
- self.init = async_to_raw_response_wrapper(resource.init)
- self.get = async_to_raw_response_wrapper(resource.get)
- self.base = async_to_raw_response_wrapper(resource.base)
- self.status = async_to_raw_response_wrapper(resource.status)
- self.diff = async_to_raw_response_wrapper(resource.diff)
-
- @cached_property
- def branch(self) -> AsyncVcsBranchResourceWithRawResponse:
- """``client.vcs.branch.with_raw_response``."""
- return AsyncVcsBranchResourceWithRawResponse(self._resource.branch)
-
-
-class AsyncVcsResourceWithStreamingResponse:
- """``client.vcs.with_streaming_response``."""
-
- def __init__(self, resource: AsyncVcsResource) -> None:
- self._resource = resource
- self.init = async_to_streamed_response_wrapper(resource.init)
- self.get = async_to_streamed_response_wrapper(resource.get)
- self.base = async_to_streamed_response_wrapper(resource.base)
- self.status = async_to_streamed_response_wrapper(resource.status)
- self.diff = async_to_streamed_response_wrapper(resource.diff)
-
- @cached_property
- def branch(self) -> AsyncVcsBranchResourceWithStreamingResponse:
- """``client.vcs.branch.with_streaming_response``."""
- return AsyncVcsBranchResourceWithStreamingResponse(self._resource.branch)
diff --git a/src/opencode/types/_generated.py b/src/opencode/types/_generated.py
deleted file mode 100644
index 660edd3..0000000
--- a/src/opencode/types/_generated.py
+++ /dev/null
@@ -1,8540 +0,0 @@
-# This file is generated by `python -m codegen` from spec/contract.json. Do not edit it by hand.
-# OpenCode 2.1.2
-
-"""Models for every OpenCode API type.
-
-Each class mirrors a schema of the OpenCode server. Attributes are snake_case; the wire names are kept
-as aliases, so models validate server JSON and serialize back to it unchanged.
-"""
-
-from __future__ import annotations
-
-from typing import Annotated, Any, ClassVar, Literal, TypeAlias
-
-from pydantic import Discriminator, Field, Tag
-
-from .._models import BaseModel, is_none
-from .._types import OpenEnum
-from .._unions import discriminator
-
-
-class AgentGetResponse(BaseModel):
- location: LocationPublicRef
-
- data: AgentInfo
-
-
-class AgentInfo(BaseModel):
- id: AgentID
-
- name: str
-
- model: ModelRef | None = Field(default=None, exclude_if=is_none)
-
- request: ProviderRequest
-
- system: str | None = Field(default=None, exclude_if=is_none)
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- mode: OpenEnum[Literal["subagent", "primary", "all"]]
-
- hidden: bool
-
- color: AgentColor | None = Field(default=None, exclude_if=is_none)
-
- steps: int | None = Field(default=None, exclude_if=is_none)
-
- permissions: PermissionRuleset
-
-
-class AgentListResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[AgentInfo]
-
-
-class AgentNotFoundErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["AgentNotFoundError"] = Field(
- default="AgentNotFoundError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- agent_id: str = Field(validation_alias="agentID", serialization_alias="agentID")
-
- message: str
-
-
-class AgentUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["agent.updated"] = "agent.updated"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: Any
-
-
-class CommandExecutionErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["CommandExecutionError"] = Field(
- default="CommandExecutionError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- command: str
-
- message: str
-
-
-class CommandInfo(BaseModel):
- name: str
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
-
-class CommandListResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[CommandInfo]
-
-
-class CommandNotFoundErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["CommandNotFoundError"] = Field(
- default="CommandNotFoundError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- command: str
-
- message: str
-
-
-class CommandUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["command.updated"] = "command.updated"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: Any
-
-
-class ConfigAgent(BaseModel):
- model: str | ConfigAgentModel | None = Field(default=None, exclude_if=is_none)
-
- request: ConfigAgentRequest | None = Field(default=None, exclude_if=is_none)
-
- system: str | None = Field(default=None, exclude_if=is_none)
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- mode: OpenEnum[Literal["subagent", "primary", "all"]] | None = Field(default=None, exclude_if=is_none)
-
- hidden: bool | None = Field(default=None, exclude_if=is_none)
-
- color: str | None = Field(default=None, exclude_if=is_none)
-
- steps: int | None = Field(default=None, exclude_if=is_none)
-
- disabled: bool | None = Field(default=None, exclude_if=is_none)
-
- permissions: PermissionRuleset | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigAgentModel(BaseModel):
- provider_id: str = Field(validation_alias="providerID", serialization_alias="providerID")
-
- model: str
-
- variant: str | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigAgentRequest(BaseModel):
- headers: dict[str, str] | None = Field(default=None, exclude_if=is_none)
-
- body: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigCommand(BaseModel):
- template: str
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- agent: str | None = Field(default=None, exclude_if=is_none)
-
- model: str | ConfigCommandModel | None = Field(default=None, exclude_if=is_none)
-
- subagent: bool | None = Field(default=None, exclude_if=is_none)
-
- subtask: bool | None = Field(default=None, exclude_if=is_none)
- """Deprecated alias for subagent."""
-
-
-class ConfigCommandModel(BaseModel):
- provider_id: str = Field(validation_alias="providerID", serialization_alias="providerID")
-
- model: str
-
- variant: str | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigDirectory(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["directory"] = "directory"
-
- path: AbsolutePath
-
-
-class ConfigDocument(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["document"] = "document"
-
- path: AbsolutePath | None = Field(default=None, exclude_if=is_none)
-
- info: ConfigInfo
-
-
-class ConfigFormatterEntry(BaseModel):
- disabled: bool | None = Field(default=None, exclude_if=is_none)
-
- command: list[str] | None = Field(default=None, exclude_if=is_none)
-
- environment: dict[str, str] | None = Field(default=None, exclude_if=is_none)
-
- extensions: list[str] | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigInfo(BaseModel):
- schema_: str | None = Field(
- default=None, validation_alias="$schema", serialization_alias="$schema", exclude_if=is_none
- )
-
- shell: str | None = Field(default=None, exclude_if=is_none)
-
- model: str | ConfigInfoModel | None = Field(default=None, exclude_if=is_none)
-
- default_agent: str | None = Field(default=None, exclude_if=is_none)
-
- update: OpenEnum[Literal["disable", "notify", "auto"]] | None = Field(default=None, exclude_if=is_none)
-
- share: OpenEnum[Literal["manual", "auto", "disabled"]] | None = Field(default=None, exclude_if=is_none)
-
- enterprise: ConfigInfoEnterprise | None = Field(default=None, exclude_if=is_none)
-
- username: str | None = Field(default=None, exclude_if=is_none)
-
- permissions: PermissionRuleset | None = Field(default=None, exclude_if=is_none)
-
- policies: list[ConfigInfoPoliciesItem] | None = Field(default=None, exclude_if=is_none)
-
- agents: dict[str, ConfigAgent] | None = Field(default=None, exclude_if=is_none)
-
- snapshots: bool | None = Field(default=None, exclude_if=is_none)
-
- watcher: ConfigInfoWatcher | None = Field(default=None, exclude_if=is_none)
-
- formatter: bool | dict[str, ConfigFormatterEntry] | None = Field(default=None, exclude_if=is_none)
-
- lsp: bool | dict[str, ConfigInfoLspOption2Value | ConfigLSPServer] | None = Field(default=None, exclude_if=is_none)
-
- media: ConfigInfoMedia | None = Field(default=None, exclude_if=is_none)
-
- tool_output: ConfigInfoToolOutput | None = Field(default=None, exclude_if=is_none)
-
- mcp: ConfigInfoMcp | None = Field(default=None, exclude_if=is_none)
-
- compaction: ConfigInfoCompaction | None = Field(default=None, exclude_if=is_none)
-
- skills: list[str] | None = Field(default=None, exclude_if=is_none)
-
- commands: dict[str, ConfigCommand] | None = Field(default=None, exclude_if=is_none)
-
- instructions: list[str] | None = Field(default=None, exclude_if=is_none)
-
- references: dict[str, str | ConfigReferenceGit | ConfigReferenceLocal] | None = Field(
- default=None, exclude_if=is_none
- )
-
- websearch: Literal[False] | ConfigWebSearchInfo | None = Field(default=None, exclude_if=is_none)
-
- plugins: list[str | ConfigPluginEntry] | None = Field(default=None, exclude_if=is_none)
-
- worktree: ConfigWorktree | None = Field(default=None, exclude_if=is_none)
-
- warming: bool | ConfigWarming | None = Field(default=None, exclude_if=is_none)
-
- providers: dict[str, ConfigProvider] | None = Field(default=None, exclude_if=is_none)
-
- experimental: ConfigInfoExperimental | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigInfoCompaction(BaseModel):
- auto: bool | None = Field(default=None, exclude_if=is_none)
-
- keep: ConfigInfoCompactionKeep | None = Field(default=None, exclude_if=is_none)
-
- buffer: int | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigInfoCompactionKeep(BaseModel):
- tokens: int | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigInfoEnterprise(BaseModel):
- url: str | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigInfoExperimental(BaseModel):
- portable_shell_scanner: bool | None = Field(default=None, exclude_if=is_none)
-
- todo: bool | None = Field(default=None, exclude_if=is_none)
-
- subagent_depth: int | None = Field(default=None, exclude_if=is_none)
-
- location_idle_timeout: str | Literal[False] | None = Field(default=None, exclude_if=is_none)
-
- policies: list[ConfigInfoExperimentalPoliciesItem] | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigInfoExperimentalPoliciesItem(BaseModel):
- action: OpenEnum[Literal["provider.use", "tool.use", "integration.use"]]
-
- resource: str
-
- effect: OpenEnum[Literal["allow", "deny"]]
-
-
-class ConfigInfoLspOption2Value(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("disabled",)
-
- disabled: Literal[True] = True
-
-
-class ConfigInfoMcp(BaseModel):
- timeout: ConfigInfoMcpTimeout | None = Field(default=None, exclude_if=is_none)
-
- servers: dict[str, ConfigInfoMcpServersValue] | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigInfoMcpTimeout(BaseModel):
- startup: int | None = Field(default=None, exclude_if=is_none)
-
- catalog: int | None = Field(default=None, exclude_if=is_none)
-
- execution: int | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigInfoMedia(BaseModel):
- image: ConfigInfoMediaImage | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigInfoMediaImage(BaseModel):
- auto_resize: bool | None = Field(default=None, exclude_if=is_none)
-
- max_width: int | None = Field(default=None, exclude_if=is_none)
-
- max_height: int | None = Field(default=None, exclude_if=is_none)
-
- max_base64_bytes: int | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigInfoModel(BaseModel):
- provider_id: str = Field(validation_alias="providerID", serialization_alias="providerID")
-
- model: str
-
- variant: str | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigInfoPoliciesItem(BaseModel):
- action: OpenEnum[Literal["provider.use", "tool.use", "integration.use"]]
-
- resource: str
-
- effect: OpenEnum[Literal["allow", "deny"]]
-
-
-class ConfigInfoToolOutput(BaseModel):
- max_lines: int | None = Field(default=None, exclude_if=is_none)
-
- max_bytes: int | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigInfoWatcher(BaseModel):
- ignore: list[str] | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigLSPServer(BaseModel):
- command: list[str]
-
- extensions: list[str] | None = Field(default=None, exclude_if=is_none)
-
- disabled: bool | None = Field(default=None, exclude_if=is_none)
-
- env: dict[str, str] | None = Field(default=None, exclude_if=is_none)
-
- initialization: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigModel(BaseModel):
- model_id: str | None = Field(
- default=None, validation_alias="modelID", serialization_alias="modelID", exclude_if=is_none
- )
-
- family: str | None = Field(default=None, exclude_if=is_none)
-
- name: str | None = Field(default=None, exclude_if=is_none)
-
- release_date: str | None = Field(
- default=None, validation_alias="releaseDate", serialization_alias="releaseDate", exclude_if=is_none
- )
- """Release date as YYYY-MM-DD or YYYY-MM"""
-
- compatibility: ModelCompatibility | None = Field(default=None, exclude_if=is_none)
-
- package: str | None = Field(default=None, exclude_if=is_none)
-
- settings: ConfigModelSettings | None = Field(default=None, exclude_if=is_none)
-
- headers: dict[str, str] | None = Field(default=None, exclude_if=is_none)
-
- body: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- capabilities: ConfigModelCapabilities | None = Field(default=None, exclude_if=is_none)
-
- variants: list[ConfigModelVariantsItem] | None = Field(default=None, exclude_if=is_none)
-
- cost: ConfigModelCost | list[ConfigModelCost] | None = Field(default=None, exclude_if=is_none)
-
- disabled: bool | None = Field(default=None, exclude_if=is_none)
-
- limit: ConfigModelLimit | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigModelCapabilities(BaseModel):
- tools: bool | None = Field(default=None, exclude_if=is_none)
-
- input: list[str] | None = Field(default=None, exclude_if=is_none)
-
- output: list[str] | None = Field(default=None, exclude_if=is_none)
-
- temperature: bool | None = Field(default=None, exclude_if=is_none)
-
- reasoning: ConfigModelReasoning | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigModelCost(BaseModel):
- tier: ConfigModelCostTier | None = Field(default=None, exclude_if=is_none)
-
- input: MoneyUSDPerMillionTokens
-
- output: MoneyUSDPerMillionTokens
-
- cache: ConfigModelCostCache | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigModelCostCache(BaseModel):
- read: MoneyUSDPerMillionTokens | None = Field(default=None, exclude_if=is_none)
-
- write: MoneyUSDPerMillionTokens | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigModelCostTier(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["context"] = "context"
-
- size: int
-
-
-class ConfigModelLimit(BaseModel):
- context: int | None = Field(default=None, exclude_if=is_none)
-
- input: int | None = Field(default=None, exclude_if=is_none)
-
- output: int | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigModelReasoningFalse(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("supported",)
-
- supported: Literal[False] = False
-
-
-class ConfigModelReasoningTrue(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("supported",)
-
- supported: Literal[True] = True
-
- toggle: bool | None = Field(default=None, exclude_if=is_none)
-
- effort: list[str] | None = Field(default=None, exclude_if=is_none)
-
- budget: ConfigModelReasoningTrueBudget | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigModelReasoningTrueBudget(BaseModel):
- min: int | None = Field(default=None, exclude_if=is_none)
-
- max: int | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigModelSettings(BaseModel):
- compaction: ProviderCompaction | None = Field(default=None, exclude_if=is_none)
-
- encoding: ProviderEncoding | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigModelVariantsItem(BaseModel):
- id: str
-
- settings: ConfigModelSettings | None = Field(default=None, exclude_if=is_none)
-
- headers: dict[str, str] | None = Field(default=None, exclude_if=is_none)
-
- body: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigPluginEntry(BaseModel):
- package: str
-
- options: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigProvider(BaseModel):
- canonical: ProviderID | None = Field(default=None, exclude_if=is_none)
-
- name: str | None = Field(default=None, exclude_if=is_none)
-
- env: list[str] | None = Field(default=None, exclude_if=is_none)
-
- package: str | None = Field(default=None, exclude_if=is_none)
-
- compatibility: ProviderCompatibility | None = Field(default=None, exclude_if=is_none)
-
- settings: ConfigProviderSettings | None = Field(default=None, exclude_if=is_none)
-
- headers: dict[str, str] | None = Field(default=None, exclude_if=is_none)
-
- body: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- models: dict[str, ConfigModel] | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigProviderSettings(BaseModel):
- timeout: float | Literal[False] | None = Field(default=None, exclude_if=is_none)
-
- header_timeout: float | Literal[False] | None = Field(
- default=None, validation_alias="headerTimeout", serialization_alias="headerTimeout", exclude_if=is_none
- )
-
- chunk_timeout: float | Literal[False] | None = Field(
- default=None, validation_alias="chunkTimeout", serialization_alias="chunkTimeout", exclude_if=is_none
- )
-
- compaction: ProviderCompaction | None = Field(default=None, exclude_if=is_none)
-
- transport: ProviderTransport | None = Field(default=None, exclude_if=is_none)
-
- encoding: ProviderEncoding | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigReferenceGit(BaseModel):
- repository: str
-
- branch: str | None = Field(default=None, exclude_if=is_none)
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- hidden: bool | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigReferenceLocal(BaseModel):
- path: str
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- hidden: bool | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigShellOption(BaseModel):
- path: str
-
- name: str
-
- acceptable: bool
-
-
-class ConfigUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["config.updated"] = "config.updated"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: Any
-
-
-class ConfigWarming(BaseModel):
- prompt: str | None = Field(default=None, exclude_if=is_none)
-
- interval: str | None = Field(default=None, exclude_if=is_none)
-
- duration: str | None = Field(default=None, exclude_if=is_none)
-
-
-class ConfigWebSearchInfo(BaseModel):
- provider: Literal["random"] | WebSearchID
- """Reuse a randomly selected provider until it is rate limited, then switch to another available provider."""
-
-
-class ConfigWorktree(BaseModel):
- directory: str
-
-
-class ConflictErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["ConflictError"] = Field(default="ConflictError", validation_alias="_tag", serialization_alias="_tag")
-
- message: str
-
- resource: str | None = None
-
-
-class ConnectionCredentialInfo(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["credential"] = "credential"
-
- id: CredentialID
-
- label: str
-
- method: OpenEnum[Literal["key", "oauth", "external"]]
-
- status: ConnectionStatus | None = Field(default=None, exclude_if=is_none)
-
-
-class ConnectionEnvInfo(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["env"] = "env"
-
- name: str
-
- status: ConnectionStatus | None = Field(default=None, exclude_if=is_none)
-
-
-class ConnectionStatus(BaseModel):
- status: Literal["needs_auth"]
-
- message: str
-
- url: str | None = Field(default=None, exclude_if=is_none)
-
-
-class CredentialEntry(BaseModel):
- id: CredentialID
-
- integration_id: IntegrationID = Field(validation_alias="integrationID", serialization_alias="integrationID")
-
- label: str
-
- active: bool
-
- value: CredentialValue
-
-
-class CredentialExternal(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["external"] = "external"
-
- method_id: IntegrationMethodID = Field(validation_alias="methodID", serialization_alias="methodID")
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
-
-class CredentialKey(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["key"] = "key"
-
- key: str
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
-
-class CredentialOAuth(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["oauth"] = "oauth"
-
- method_id: IntegrationMethodID = Field(validation_alias="methodID", serialization_alias="methodID")
-
- refresh: str
-
- access: str
-
- expires: int
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
-
-class CredentialSwitchedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["credential.switched"] = "credential.switched"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: CredentialSwitchedEventData
-
-
-class CredentialSwitchedEventData(BaseModel):
- integration_id: IntegrationID = Field(validation_alias="integrationID", serialization_alias="integrationID")
-
- credential_id: CredentialID | None = Field(validation_alias="credentialID", serialization_alias="credentialID")
-
-
-class CredentialUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["credential.updated"] = "credential.updated"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: Any
-
-
-class EventLogSynced(BaseModel):
- """Marker emitted once when a log read reaches its captured watermark. The reader holds every event committed at or below seq."""
-
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["log.synced"] = "log.synced"
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq | None = Field(default=None, exclude_if=is_none)
-
-
-class ExperimentalPersistentPtyCreateSize(BaseModel):
- cols: int
-
- rows: int
-
-
-class ExperimentalPersistentPtyHandoffResponse(BaseModel):
- handoff: PersistentPtyHandoff | None
-
-
-class ExperimentalPersistentPtyUpdateSize(BaseModel):
- cols: int
-
- rows: int
-
-
-class FileDiffInfo(BaseModel):
- file: str
-
- patch: str
-
- additions: int
-
- deletions: int
-
- status: OpenEnum[Literal["added", "deleted", "modified"]]
-
-
-class FileFindResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[FileSystemEntry]
-
-
-class FileListResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[FileSystemEntry]
-
-
-class FileNotFoundErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["FileNotFoundError"] = Field(
- default="FileNotFoundError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- path: str
-
- message: str
-
-
-class FileSystemEntry(BaseModel):
- path: RelativePath
-
- type: OpenEnum[Literal["file", "directory"]]
-
-
-class FileSystemWrite(BaseModel):
- path: AbsolutePath
-
-
-class FileWriteResponse(BaseModel):
- location: LocationPublicRef
-
- data: FileSystemWrite
-
-
-class FilesystemChangedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["filesystem.changed"] = "filesystem.changed"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: FilesystemChangedEventData
-
-
-class FilesystemChangedEventData(BaseModel):
- file: str
-
- event: OpenEnum[Literal["add", "change", "unlink"]]
-
-
-class ForbiddenErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["ForbiddenError"] = Field(
- default="ForbiddenError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- message: str
-
-
-class FormAlreadySettledErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["FormAlreadySettledError"] = Field(
- default="FormAlreadySettledError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- id: str
-
- message: str
-
-
-class FormBooleanField(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- key: str
-
- title: str | None = Field(default=None, exclude_if=is_none)
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- required: bool | None = Field(default=None, exclude_if=is_none)
-
- hidden: bool | None = Field(default=None, exclude_if=is_none)
-
- when: list[FormWhen] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["boolean"] = "boolean"
-
- default: bool | None = Field(default=None, exclude_if=is_none)
-
-
-class FormCancelledEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["form.cancelled"] = "form.cancelled"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: FormCancelledEventData
-
-
-class FormCancelledEventData(BaseModel):
- id: FormID
-
- session_id: str = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
-
-class FormCreatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["form.created"] = "form.created"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: FormCreatedEventData
-
-
-class FormCreatedEventData(BaseModel):
- form: FormInfo
-
-
-class FormDetail(BaseModel):
- id: FormID
-
- session_id: str = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- title: str
-
- metadata: FormMetadata | None = Field(default=None, exclude_if=is_none)
-
- fields: FormFields
-
- state: FormState
-
-
-class FormExternalField(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- key: str
-
- type: Literal["external"] = "external"
-
- url: str
-
- title: str | None = Field(default=None, exclude_if=is_none)
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
-
-class FormInfo(BaseModel):
- id: FormID
-
- session_id: str = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- title: str
-
- metadata: FormMetadata | None = Field(default=None, exclude_if=is_none)
-
- fields: FormFields
-
-
-class FormIntegerField(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- key: str
-
- title: str | None = Field(default=None, exclude_if=is_none)
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- required: bool | None = Field(default=None, exclude_if=is_none)
-
- hidden: bool | None = Field(default=None, exclude_if=is_none)
-
- when: list[FormWhen] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["integer"] = "integer"
-
- minimum: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]] | None = Field(default=None, exclude_if=is_none)
-
- maximum: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]] | None = Field(default=None, exclude_if=is_none)
-
- default: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]] | None = Field(default=None, exclude_if=is_none)
-
-
-class FormInvalidAnswerErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["FormInvalidAnswerError"] = Field(
- default="FormInvalidAnswerError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- id: str
-
- message: str
-
-
-class FormListResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[FormInfo]
-
-
-class FormMultiselectField(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- key: str
-
- title: str | None = Field(default=None, exclude_if=is_none)
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- required: bool | None = Field(default=None, exclude_if=is_none)
-
- hidden: bool | None = Field(default=None, exclude_if=is_none)
-
- when: list[FormWhen] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["multiselect"] = "multiselect"
-
- options: list[FormOption]
-
- min_items: int | None = Field(
- default=None, validation_alias="minItems", serialization_alias="minItems", exclude_if=is_none
- )
-
- max_items: int | None = Field(
- default=None, validation_alias="maxItems", serialization_alias="maxItems", exclude_if=is_none
- )
-
- custom: bool | None = Field(default=None, exclude_if=is_none)
-
- default: list[str] | None = Field(default=None, exclude_if=is_none)
-
-
-class FormNotFoundErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["FormNotFoundError"] = Field(
- default="FormNotFoundError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- id: str
-
- message: str
-
-
-class FormNumberField(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- key: str
-
- title: str | None = Field(default=None, exclude_if=is_none)
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- required: bool | None = Field(default=None, exclude_if=is_none)
-
- hidden: bool | None = Field(default=None, exclude_if=is_none)
-
- when: list[FormWhen] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["number"] = "number"
-
- minimum: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]] | None = Field(default=None, exclude_if=is_none)
-
- maximum: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]] | None = Field(default=None, exclude_if=is_none)
-
- default: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]] | None = Field(default=None, exclude_if=is_none)
-
-
-class FormOption(BaseModel):
- value: str
-
- label: str
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
-
-class FormRepliedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["form.replied"] = "form.replied"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: FormRepliedEventData
-
-
-class FormRepliedEventData(BaseModel):
- id: FormID
-
- session_id: str = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- answer: FormAnswer
-
-
-class FormStateAnswered(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["answered"] = "answered"
-
- answer: FormAnswer
-
-
-class FormStateCancelled(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["cancelled"] = "cancelled"
-
- message: str | None = Field(default=None, exclude_if=is_none)
-
-
-class FormStatePending(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["pending"] = "pending"
-
-
-class FormStringField(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- key: str
-
- title: str | None = Field(default=None, exclude_if=is_none)
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- required: bool | None = Field(default=None, exclude_if=is_none)
-
- hidden: bool | None = Field(default=None, exclude_if=is_none)
-
- when: list[FormWhen] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["string"] = "string"
-
- format: OpenEnum[Literal["email", "uri", "date", "date-time"]] | None = Field(default=None, exclude_if=is_none)
-
- min_length: int | None = Field(
- default=None, validation_alias="minLength", serialization_alias="minLength", exclude_if=is_none
- )
-
- max_length: int | None = Field(
- default=None, validation_alias="maxLength", serialization_alias="maxLength", exclude_if=is_none
- )
-
- pattern: str | None = Field(default=None, exclude_if=is_none)
-
- placeholder: str | None = Field(default=None, exclude_if=is_none)
-
- default: str | None = Field(default=None, exclude_if=is_none)
-
- options: list[FormOption] | None = Field(default=None, exclude_if=is_none)
-
- custom: bool | None = Field(default=None, exclude_if=is_none)
-
-
-class FormWhen(BaseModel):
- key: str
-
- op: OpenEnum[Literal["eq", "neq"]]
-
- value: str | float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]] | bool
-
-
-class GenerateTextResponse(BaseModel):
- data: GenerateTextResponseData
-
-
-class GenerateTextResponseData(BaseModel):
- text: str
-
-
-class InstallationUpdateAvailableEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["installation.update-available"] = "installation.update-available"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: InstallationUpdateAvailableEventData
-
-
-class InstallationUpdateAvailableEventData(BaseModel):
- version: str
-
-
-class InstallationUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["installation.updated"] = "installation.updated"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: InstallationUpdatedEventData
-
-
-class InstallationUpdatedEventData(BaseModel):
- version: str
-
-
-class InstructionEntryInfo(BaseModel):
- key: InstructionEntryKey
-
- value: Any
- """JSON value attached to the session's instructions"""
-
-
-class InstructionEntrySnapshotItem(BaseModel):
- key: InstructionEntryKey
-
- value: Any
- """JSON value attached to the session's instructions"""
-
- removed: bool
-
-
-class InstructionEntryValueTooLargeErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["InstructionEntryValueTooLargeError"] = Field(
- default="InstructionEntryValueTooLargeError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- actual_bytes: int = Field(validation_alias="actualBytes", serialization_alias="actualBytes")
-
- max_bytes: int = Field(validation_alias="maxBytes", serialization_alias="maxBytes")
-
- message: str
-
-
-class IntegrationAttempt(BaseModel):
- attempt_id: IntegrationAttemptID = Field(validation_alias="attemptID", serialization_alias="attemptID")
-
- url: str
-
- instructions: str
-
- mode: OpenEnum[Literal["auto", "code"]]
-
- time: IntegrationAttemptTime
-
-
-class IntegrationAttemptNotFoundErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["IntegrationAttemptNotFoundError"] = Field(
- default="IntegrationAttemptNotFoundError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- integration_id: str = Field(validation_alias="integrationID", serialization_alias="integrationID")
-
- attempt_id: str = Field(validation_alias="attemptID", serialization_alias="attemptID")
-
- message: str
-
-
-class IntegrationAttemptStatusComplete(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["complete"] = "complete"
-
- time: IntegrationAttemptStatusCompleteTime
-
-
-class IntegrationAttemptStatusCompleteTime(BaseModel):
- created: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
- expires: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
-
-class IntegrationAttemptStatusExpired(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["expired"] = "expired"
-
- time: IntegrationAttemptStatusExpiredTime
-
-
-class IntegrationAttemptStatusExpiredTime(BaseModel):
- created: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
- expires: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
-
-class IntegrationAttemptStatusFailed(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["failed"] = "failed"
-
- message: str
-
- time: IntegrationAttemptStatusFailedTime
-
-
-class IntegrationAttemptStatusFailedTime(BaseModel):
- created: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
- expires: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
-
-class IntegrationAttemptStatusPending(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["pending"] = "pending"
-
- time: IntegrationAttemptStatusPendingTime
-
-
-class IntegrationAttemptStatusPendingTime(BaseModel):
- created: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
- expires: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
-
-class IntegrationAttemptTime(BaseModel):
- created: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
- expires: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
-
-class IntegrationCommandAttempt(BaseModel):
- attempt_id: IntegrationAttemptID = Field(validation_alias="attemptID", serialization_alias="attemptID")
-
- time: IntegrationCommandAttemptTime
-
-
-class IntegrationCommandAttemptStatusComplete(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["complete"] = "complete"
-
- time: IntegrationCommandAttemptStatusCompleteTime
-
-
-class IntegrationCommandAttemptStatusCompleteTime(BaseModel):
- created: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
- expires: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
-
-class IntegrationCommandAttemptStatusExpired(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["expired"] = "expired"
-
- time: IntegrationCommandAttemptStatusExpiredTime
-
-
-class IntegrationCommandAttemptStatusExpiredTime(BaseModel):
- created: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
- expires: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
-
-class IntegrationCommandAttemptStatusFailed(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["failed"] = "failed"
-
- message: str
-
- time: IntegrationCommandAttemptStatusFailedTime
-
-
-class IntegrationCommandAttemptStatusFailedTime(BaseModel):
- created: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
- expires: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
-
-class IntegrationCommandAttemptStatusPending(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["pending"] = "pending"
-
- message: str | None = Field(default=None, exclude_if=is_none)
-
- time: IntegrationCommandAttemptStatusPendingTime
-
-
-class IntegrationCommandAttemptStatusPendingTime(BaseModel):
- created: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
- expires: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
-
-class IntegrationCommandAttemptTime(BaseModel):
- created: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
- expires: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]]
-
-
-class IntegrationCommandConnectResponse(BaseModel):
- location: LocationPublicRef
-
- data: IntegrationCommandAttempt
-
-
-class IntegrationCommandMethod(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: IntegrationMethodID
-
- type: Literal["command"] = "command"
-
- label: str
-
- command: list[str]
-
-
-class IntegrationCommandStatusResponse(BaseModel):
- location: LocationPublicRef
-
- data: IntegrationCommandAttemptStatus
-
-
-class IntegrationEnvMethod(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["env"] = "env"
-
- names: list[str]
-
-
-class IntegrationExternalMethod(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: IntegrationMethodID
-
- type: Literal["external"] = "external"
-
- label: str
-
- form: FormFields | None = Field(default=None, exclude_if=is_none)
-
-
-class IntegrationGetResponse(BaseModel):
- location: LocationPublicRef
-
- data: IntegrationInfo
-
-
-class IntegrationInfo(BaseModel):
- id: IntegrationID
-
- name: str
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- methods: list[IntegrationMethod]
-
- connections: list[ConnectionInfo]
-
-
-class IntegrationKeyMethod(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["key"] = "key"
-
- label: str | None = Field(default=None, exclude_if=is_none)
-
- form: FormFields | None = Field(default=None, exclude_if=is_none)
-
-
-class IntegrationListResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[IntegrationInfo]
-
-
-class IntegrationMethodNotFoundErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["IntegrationMethodNotFoundError"] = Field(
- default="IntegrationMethodNotFoundError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- integration_id: str = Field(validation_alias="integrationID", serialization_alias="integrationID")
-
- method_id: str = Field(validation_alias="methodID", serialization_alias="methodID")
-
- message: str
-
-
-class IntegrationNotFoundErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["IntegrationNotFoundError"] = Field(
- default="IntegrationNotFoundError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- integration_id: str = Field(validation_alias="integrationID", serialization_alias="integrationID")
-
- message: str
-
-
-class IntegrationOAuthMethod(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: IntegrationMethodID
-
- type: Literal["oauth"] = "oauth"
-
- label: str
-
- form: FormFields | None = Field(default=None, exclude_if=is_none)
-
-
-class IntegrationOauthConnectResponse(BaseModel):
- location: LocationPublicRef
-
- data: IntegrationAttempt
-
-
-class IntegrationOauthStatusResponse(BaseModel):
- location: LocationPublicRef
-
- data: IntegrationAttemptStatus
-
-
-class IntegrationUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["integration.updated"] = "integration.updated"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: Any
-
-
-class InvalidCursorErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["InvalidCursorError"] = Field(
- default="InvalidCursorError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- message: str
-
-
-class InvalidRequestErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["InvalidRequestError"] = Field(
- default="InvalidRequestError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- message: str
-
- kind: str | None = None
-
- field: str | None = None
-
-
-class Location(BaseModel):
- directory: str | None = None
-
-
-class LocationNotFoundErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["LocationNotFoundError"] = Field(
- default="LocationNotFoundError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- location: LocationPublicRef
-
- message: str
-
-
-class LocationPublicInfo(BaseModel):
- directory: AbsolutePath
-
- project: LocationPublicInfoProject
-
-
-class LocationPublicInfoProject(BaseModel):
- id: ProjectID
-
- directory: AbsolutePath
-
- canonical: AbsolutePath
-
-
-class LocationPublicRef(BaseModel):
- directory: AbsolutePath
-
-
-class LocationRef(BaseModel):
- directory: AbsolutePath
-
- workspace_id: WorkspaceID | None = Field(
- default=None, validation_alias="workspaceID", serialization_alias="workspaceID", exclude_if=is_none
- )
-
-
-class LocationShutdownEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["location.shutdown"] = "location.shutdown"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: Any
-
-
-class McpListResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[McpServer]
-
-
-class McpLocalConfig(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["local"] = "local"
-
- command: list[str]
-
- cwd: str | None = Field(default=None, exclude_if=is_none)
-
- environment: dict[str, str] | None = Field(default=None, exclude_if=is_none)
-
- disabled: bool | None = Field(default=None, exclude_if=is_none)
-
- codemode: bool | None = Field(default=None, exclude_if=is_none)
-
- timeout: McpLocalConfigTimeout | None = Field(default=None, exclude_if=is_none)
-
- protocol: McpProtocol | None = Field(default=None, exclude_if=is_none)
-
-
-class McpLocalConfigTimeout(BaseModel):
- startup: int | None = Field(default=None, exclude_if=is_none)
-
- catalog: int | None = Field(default=None, exclude_if=is_none)
-
- execution: int | None = Field(default=None, exclude_if=is_none)
-
-
-class McpOAuthConfig(BaseModel):
- client_id: str | None = Field(default=None, exclude_if=is_none)
-
- client_secret: str | None = Field(default=None, exclude_if=is_none)
-
- scope: str | None = Field(default=None, exclude_if=is_none)
-
- callback_port: int | None = Field(default=None, exclude_if=is_none)
-
- redirect_uri: str | None = Field(default=None, exclude_if=is_none)
-
- auth_server_metadata_url: str | None = Field(default=None, exclude_if=is_none)
-
-
-class McpRemoteConfig(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["remote"] = "remote"
-
- url: str
-
- headers: dict[str, str] | None = Field(default=None, exclude_if=is_none)
-
- oauth: McpOAuthConfig | Literal[False] | None = Field(default=None, exclude_if=is_none)
-
- disabled: bool | None = Field(default=None, exclude_if=is_none)
-
- codemode: bool | None = Field(default=None, exclude_if=is_none)
-
- timeout: McpRemoteConfigTimeout | None = Field(default=None, exclude_if=is_none)
-
- protocol: McpProtocol | None = Field(default=None, exclude_if=is_none)
-
-
-class McpRemoteConfigTimeout(BaseModel):
- startup: int | None = Field(default=None, exclude_if=is_none)
-
- catalog: int | None = Field(default=None, exclude_if=is_none)
-
- execution: int | None = Field(default=None, exclude_if=is_none)
-
-
-class McpResource(BaseModel):
- server: str
-
- name: str
-
- uri: str
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- mime_type: str | None = Field(
- default=None, validation_alias="mimeType", serialization_alias="mimeType", exclude_if=is_none
- )
-
-
-class McpResourceCatalog(BaseModel):
- resources: list[McpResource]
-
- templates: list[McpResourceTemplate]
-
-
-class McpResourceCatalogResponse(BaseModel):
- location: LocationPublicRef
-
- data: McpResourceCatalog
-
-
-class McpResourceTemplate(BaseModel):
- server: str
-
- name: str
-
- uri_template: str = Field(validation_alias="uriTemplate", serialization_alias="uriTemplate")
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- mime_type: str | None = Field(
- default=None, validation_alias="mimeType", serialization_alias="mimeType", exclude_if=is_none
- )
-
-
-class McpResourcesChangedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["mcp.resources.changed"] = "mcp.resources.changed"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: McpResourcesChangedEventData
-
-
-class McpResourcesChangedEventData(BaseModel):
- server: str
-
-
-class McpServer(BaseModel):
- name: str
-
- status: McpServerStatus
-
- integration_id: IntegrationID | None = Field(
- default=None, validation_alias="integrationID", serialization_alias="integrationID", exclude_if=is_none
- )
-
-
-class McpServerNotFoundErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["McpServerNotFoundError"] = Field(
- default="McpServerNotFoundError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- server: str
-
- message: str
-
-
-class McpStatusChangedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["mcp.status.changed"] = "mcp.status.changed"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: McpStatusChangedEventData
-
-
-class McpStatusChangedEventData(BaseModel):
- server: str
-
-
-class McpStatusConnected(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["connected"] = "connected"
-
-
-class McpStatusDisabled(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["disabled"] = "disabled"
-
-
-class McpStatusFailed(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["failed"] = "failed"
-
- error: str
-
-
-class McpStatusNeedsAuth(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["needs_auth"] = "needs_auth"
-
- error: str
-
-
-class McpStatusPending(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["pending"] = "pending"
-
-
-class MessageNotFoundErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["MessageNotFoundError"] = Field(
- default="MessageNotFoundError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- session_id: str = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- message_id: str = Field(validation_alias="messageID", serialization_alias="messageID")
-
- message: str
-
-
-class MigrationV1StatusResponseOption1(BaseModel):
- status: OpenEnum[Literal["required", "completed"]]
-
-
-class MigrationV1StatusResponseOption2(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["running"] = "running"
-
- progress: MigrationV1StatusResponseOption2Progress
-
-
-class MigrationV1StatusResponseOption2Progress(BaseModel):
- label: str
-
- numerator: int | None = None
-
- denominator: int | None = None
-
-
-class MigrationV1StatusResponseOption3(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["error"] = "error"
-
- error: str
-
-
-class ModelCapabilities(BaseModel):
- tools: bool
-
- input: list[str]
-
- output: list[str]
-
- temperature: bool | None = Field(default=None, exclude_if=is_none)
-
-
-class ModelCompatibility(BaseModel):
- reasoning_field: ModelReasoningField | None = Field(
- default=None, validation_alias="reasoningField", serialization_alias="reasoningField", exclude_if=is_none
- )
-
- require_reasoning: bool | None = Field(
- default=None, validation_alias="requireReasoning", serialization_alias="requireReasoning", exclude_if=is_none
- )
-
- max_tokens_field: ModelMaxTokensField | None = Field(
- default=None, validation_alias="maxTokensField", serialization_alias="maxTokensField", exclude_if=is_none
- )
-
- require_finish_reason: bool | None = Field(
- default=None,
- validation_alias="requireFinishReason",
- serialization_alias="requireFinishReason",
- exclude_if=is_none,
- )
-
- require_assistant_after_tool: bool | None = Field(
- default=None,
- validation_alias="requireAssistantAfterTool",
- serialization_alias="requireAssistantAfterTool",
- exclude_if=is_none,
- )
-
- supports_prompt_cache_key: bool | None = Field(
- default=None,
- validation_alias="supportsPromptCacheKey",
- serialization_alias="supportsPromptCacheKey",
- exclude_if=is_none,
- )
-
- stream_usage: bool | None = Field(
- default=None, validation_alias="streamUsage", serialization_alias="streamUsage", exclude_if=is_none
- )
-
- cache_format: ModelCacheFormat | None = Field(
- default=None, validation_alias="cacheFormat", serialization_alias="cacheFormat", exclude_if=is_none
- )
-
- supports_thinking_block_binding: bool | None = Field(
- default=None,
- validation_alias="supportsThinkingBlockBinding",
- serialization_alias="supportsThinkingBlockBinding",
- exclude_if=is_none,
- )
-
- supports_effort_updates: bool | None = Field(
- default=None,
- validation_alias="supportsEffortUpdates",
- serialization_alias="supportsEffortUpdates",
- exclude_if=is_none,
- )
-
-
-class ModelCost(BaseModel):
- tier: ModelCostTier | None = Field(default=None, exclude_if=is_none)
-
- input: MoneyUSDPerMillionTokens
-
- output: MoneyUSDPerMillionTokens
-
- cache: ModelCostCache
-
-
-class ModelCostCache(BaseModel):
- read: MoneyUSDPerMillionTokens
-
- write: MoneyUSDPerMillionTokens
-
-
-class ModelCostTier(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["context"] = "context"
-
- size: int
-
-
-class ModelDefaultResponse(BaseModel):
- location: LocationPublicRef
-
- data: ModelInfo | None
-
-
-class ModelInfo(BaseModel):
- id: str
-
- model_id: str = Field(validation_alias="modelID", serialization_alias="modelID")
-
- provider_id: ProviderID = Field(validation_alias="providerID", serialization_alias="providerID")
-
- canonical: ProviderID | None = Field(default=None, exclude_if=is_none)
-
- family: str | None = Field(default=None, exclude_if=is_none)
-
- name: str
-
- compatibility: ModelCompatibility | None = Field(default=None, exclude_if=is_none)
-
- package: str | None = Field(default=None, exclude_if=is_none)
-
- settings: ModelSettings | None = Field(default=None, exclude_if=is_none)
-
- headers: dict[str, str] | None = Field(default=None, exclude_if=is_none)
-
- body: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- capabilities: ModelCapabilities
-
- variants: list[ModelVariant]
-
- time: ModelInfoTime
-
- cost: list[ModelCost]
-
- status: OpenEnum[Literal["alpha", "beta", "deprecated", "active"]]
-
- enabled: bool
-
- limit: ModelInfoLimit
-
-
-class ModelInfoLimit(BaseModel):
- context: int
-
- input: int | None = Field(default=None, exclude_if=is_none)
-
- output: int
-
-
-class ModelInfoTime(BaseModel):
- released: float
-
-
-class ModelListResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[ModelInfo]
-
-
-class ModelRef(BaseModel):
- id: str
-
- provider_id: ProviderID = Field(validation_alias="providerID", serialization_alias="providerID")
-
- variant: str | None = Field(default=None, exclude_if=is_none)
-
-
-class ModelSettings(BaseModel):
- compaction: ProviderCompaction | None = Field(default=None, exclude_if=is_none)
-
- encoding: ProviderEncoding | None = Field(default=None, exclude_if=is_none)
-
-
-class ModelUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["model.updated"] = "model.updated"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: Any
-
-
-class ModelVariant(BaseModel):
- id: str
-
- settings: ModelSettings | None = Field(default=None, exclude_if=is_none)
-
- headers: dict[str, str] | None = Field(default=None, exclude_if=is_none)
-
- body: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
-
-class ModelsDevRefreshedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["models-dev.refreshed"] = "models-dev.refreshed"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: Any
-
-
-class PairingCode(BaseModel):
- code: str
-
- expires_in: int
-
-
-class PairingSession(BaseModel):
- token: str
-
-
-class PermissionAskedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["permission.asked"] = "permission.asked"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: PermissionAskedEventData
-
-
-class PermissionAskedEventData(BaseModel):
- id: PermissionID
-
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- action: str
-
- resources: list[str]
-
- save: list[str] | None = Field(default=None, exclude_if=is_none)
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- source: PermissionSource | None = Field(default=None, exclude_if=is_none)
-
- message: str | None = Field(default=None, exclude_if=is_none)
-
-
-class PermissionCreateResponse(BaseModel):
- id: PermissionID
-
- effect: PermissionEffect
-
-
-class PermissionNotFoundErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["PermissionNotFoundError"] = Field(
- default="PermissionNotFoundError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- request_id: str = Field(validation_alias="requestID", serialization_alias="requestID")
-
- message: str
-
-
-class PermissionRepliedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["permission.replied"] = "permission.replied"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: PermissionRepliedEventData
-
-
-class PermissionRepliedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- request_id: PermissionID = Field(validation_alias="requestID", serialization_alias="requestID")
-
- reply: PermissionReply
-
-
-class PermissionRequest(BaseModel):
- id: PermissionID
-
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- action: str
-
- resources: list[str]
-
- save: list[str] | None = Field(default=None, exclude_if=is_none)
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- source: PermissionSource | None = Field(default=None, exclude_if=is_none)
-
- message: str | None = Field(default=None, exclude_if=is_none)
-
-
-class PermissionRequestListResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[PermissionRequest]
-
-
-class PermissionRule(BaseModel):
- action: str
-
- resource: str
-
- effect: PermissionEffect
-
-
-class PermissionSavedInfo(BaseModel):
- id: PermissionSavedID
-
- project_id: ProjectID = Field(validation_alias="projectID", serialization_alias="projectID")
-
- action: str
-
- resource: str
-
- time: PermissionSavedInfoTime
-
-
-class PermissionSavedInfoTime(BaseModel):
- created: float
-
- updated: float
-
-
-class PermissionSavedNotFoundErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["PermissionSavedNotFoundError"] = Field(
- default="PermissionSavedNotFoundError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- id: str
-
- message: str
-
-
-class PermissionSource(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["tool"] = "tool"
-
- message_id: str = Field(validation_alias="messageID", serialization_alias="messageID")
-
- id: str
-
-
-class PersistentPtyAddedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["persistent-pty.added"] = "persistent-pty.added"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: PersistentPtyAddedEventData
-
-
-class PersistentPtyAddedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- terminal: PersistentPtyInfo
-
-
-class PersistentPtyHandoff(BaseModel):
- directory: str
-
- instance_id: str = Field(validation_alias="instanceID", serialization_alias="instanceID")
-
- ticket: str
-
- expires_at: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]] = Field(
- validation_alias="expiresAt", serialization_alias="expiresAt"
- )
-
-
-class PersistentPtyInfo(BaseModel):
- id: PtyID
-
- title: str
-
- command: str
-
- args: list[str]
-
- cwd: str
-
- status: OpenEnum[Literal["running", "exited"]]
-
- pid: int
-
- exit_code: int | None = Field(
- default=None, validation_alias="exitCode", serialization_alias="exitCode", exclude_if=is_none
- )
-
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- foreground_process: str | None = Field(
- validation_alias="foregroundProcess", serialization_alias="foregroundProcess"
- )
-
- size: PersistentPtyInfoSize
-
- output: PersistentPtyInfoOutput
-
-
-class PersistentPtyInfoOutput(BaseModel):
- head: int
-
- tail: int
-
-
-class PersistentPtyInfoSize(BaseModel):
- cols: int
-
- rows: int
-
-
-class PersistentPtyReadResult(BaseModel):
- pty_id: PtyID = Field(validation_alias="ptyID", serialization_alias="ptyID")
-
- title: str
-
- cwd: str
-
- foreground_process: str | None = Field(
- validation_alias="foregroundProcess", serialization_alias="foregroundProcess"
- )
-
- screen: PersistentPtyReadResultScreen
-
-
-class PersistentPtyReadResultScreen(BaseModel):
- text: str
-
- cols: int
-
- rows: int
-
- cursor: PersistentPtyReadResultScreenCursor
-
-
-class PersistentPtyReadResultScreenCursor(BaseModel):
- x: int
-
- y: int
-
-
-class PersistentPtyRemovedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["persistent-pty.removed"] = "persistent-pty.removed"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: PersistentPtyRemovedEventData
-
-
-class PersistentPtyRemovedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- pty_id: PtyID = Field(validation_alias="ptyID", serialization_alias="ptyID")
-
-
-class PersistentPtySnapshot(BaseModel):
- info: PersistentPtyInfo
-
- text: str
-
- checkpoint: bytes
-
- cursor: PersistentPtySnapshotCursor
-
-
-class PersistentPtySnapshotCursor(BaseModel):
- x: int
-
- y: int
-
-
-class PluginCheckResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[PluginInfo]
-
-
-class PluginFeatures(BaseModel):
- server: Literal[True] | None = Field(default=None, exclude_if=is_none)
-
- tui: Literal[True] | None = Field(default=None, exclude_if=is_none)
-
- rpc: Literal[True] | None = Field(default=None, exclude_if=is_none)
-
-
-class PluginInfo(BaseModel):
- id: str | None = Field(default=None, exclude_if=is_none)
-
- source: PluginSource
-
- features: PluginFeatures
-
- state: PluginState
-
-
-class PluginListResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[PluginInfo]
-
-
-class PluginSourceBuiltin(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["builtin"] = "builtin"
-
-
-class PluginSourceLocal(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["local"] = "local"
-
- path: str
-
-
-class PluginSourcePackage(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["package"] = "package"
-
- target: str
-
- version: str | None = Field(default=None, exclude_if=is_none)
-
- outdated: Literal[True] | None = Field(default=None, exclude_if=is_none)
-
- updating: Literal[True] | None = Field(default=None, exclude_if=is_none)
-
-
-class PluginSourceSdk(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["sdk"] = "sdk"
-
-
-class PluginStateActive(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["active"] = "active"
-
-
-class PluginStateFailed(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["failed"] = "failed"
-
- error: str
-
- ref: str | None = Field(default=None, exclude_if=is_none)
-
-
-class PluginUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["plugin.updated"] = "plugin.updated"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: Any
-
-
-class Project(BaseModel):
- id: ProjectID
-
- canonical: AbsolutePath
-
- vcs: ProjectVcs | None = Field(default=None, exclude_if=is_none)
-
- name: str | None = Field(default=None, exclude_if=is_none)
-
- icon: ProjectIcon | None = Field(default=None, exclude_if=is_none)
-
- commands: ProjectCommands | None = Field(default=None, exclude_if=is_none)
-
- time: ProjectTime
-
- sandboxes: list[str]
-
-
-class ProjectCommands(BaseModel):
- start: str | None = Field(default=None, exclude_if=is_none)
- """Startup script to run when creating a new workspace (worktree)"""
-
-
-class ProjectIcon(BaseModel):
- url: str | None = Field(default=None, exclude_if=is_none)
-
- override: str | None = Field(default=None, exclude_if=is_none)
-
- color: str | None = Field(default=None, exclude_if=is_none)
-
-
-class ProjectNotFoundErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["ProjectNotFoundError"] = Field(
- default="ProjectNotFoundError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- project_id: str = Field(validation_alias="projectID", serialization_alias="projectID")
-
- message: str
-
-
-class ProjectTime(BaseModel):
- created: int
-
- updated: int
-
- active: int
-
- archived: int | None = Field(default=None, exclude_if=is_none)
-
-
-class ProjectUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["project.updated"] = "project.updated"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: ProjectUpdatedEventData
-
-
-class ProjectUpdatedEventData(BaseModel):
- id: ProjectID
-
- canonical: AbsolutePath
-
- vcs: ProjectVcs | None = Field(default=None, exclude_if=is_none)
-
- name: str | None = Field(default=None, exclude_if=is_none)
-
- icon: ProjectIcon | None = Field(default=None, exclude_if=is_none)
-
- commands: ProjectCommands | None = Field(default=None, exclude_if=is_none)
-
- time: ProjectTime
-
- sandboxes: list[str]
-
-
-class PromptAgentAttachment(BaseModel):
- name: str
-
- mention: PromptMention | None = Field(default=None, exclude_if=is_none)
-
-
-class PromptFileAttachment(BaseModel):
- data: PromptBase64
-
- mime: str
-
- source: PromptFileSource
-
- name: str | None = Field(default=None, exclude_if=is_none)
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- mention: PromptMention | None = Field(default=None, exclude_if=is_none)
-
-
-class PromptFileSourceInline(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["inline"] = "inline"
-
-
-class PromptFileSourceUri(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["uri"] = "uri"
-
- uri: str
-
-
-class PromptInputFileAttachment(BaseModel):
- uri: str
-
- name: str | None = Field(default=None, exclude_if=is_none)
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- mention: PromptMention | None = Field(default=None, exclude_if=is_none)
-
-
-class PromptInputSkillAttachment(BaseModel):
- id: SkillID
-
- mention: PromptMention | None = Field(default=None, exclude_if=is_none)
-
-
-class PromptMention(BaseModel):
- start: float
-
- end: float
-
- text: str
-
-
-class PromptSkillAttachment(BaseModel):
- id: SkillID
-
- name: SkillName
-
- text: str | None = Field(default=None, exclude_if=is_none)
-
- mention: PromptMention | None = Field(default=None, exclude_if=is_none)
-
-
-class ProviderCompactionNative(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["native"] = "native"
-
-
-class ProviderCompactionSummary(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["summary"] = "summary"
-
-
-class ProviderCompatibility(BaseModel):
- reasoning_field: ModelReasoningField | None = Field(
- default=None, validation_alias="reasoningField", serialization_alias="reasoningField", exclude_if=is_none
- )
-
- require_reasoning: bool | None = Field(
- default=None, validation_alias="requireReasoning", serialization_alias="requireReasoning", exclude_if=is_none
- )
-
- max_tokens_field: ModelMaxTokensField | None = Field(
- default=None, validation_alias="maxTokensField", serialization_alias="maxTokensField", exclude_if=is_none
- )
-
- require_finish_reason: bool | None = Field(
- default=None,
- validation_alias="requireFinishReason",
- serialization_alias="requireFinishReason",
- exclude_if=is_none,
- )
-
- require_assistant_after_tool: bool | None = Field(
- default=None,
- validation_alias="requireAssistantAfterTool",
- serialization_alias="requireAssistantAfterTool",
- exclude_if=is_none,
- )
-
- supports_prompt_cache_key: bool | None = Field(
- default=None,
- validation_alias="supportsPromptCacheKey",
- serialization_alias="supportsPromptCacheKey",
- exclude_if=is_none,
- )
-
- stream_usage: bool | None = Field(
- default=None, validation_alias="streamUsage", serialization_alias="streamUsage", exclude_if=is_none
- )
-
- cache_format: ModelCacheFormat | None = Field(
- default=None, validation_alias="cacheFormat", serialization_alias="cacheFormat", exclude_if=is_none
- )
-
-
-class ProviderGetResponse(BaseModel):
- location: LocationPublicRef
-
- data: ProviderInfo
-
-
-class ProviderInfo(BaseModel):
- id: ProviderID
-
- canonical: ProviderID | None = Field(default=None, exclude_if=is_none)
-
- integration_id: IntegrationID | None = Field(
- default=None, validation_alias="integrationID", serialization_alias="integrationID", exclude_if=is_none
- )
-
- name: str
-
- activation: OpenEnum[Literal["auto", "enabled", "disabled"]]
-
- package: str
-
- compatibility: ProviderCompatibility | None = Field(default=None, exclude_if=is_none)
-
- settings: ProviderSettings | None = Field(default=None, exclude_if=is_none)
-
- headers: dict[str, str] | None = Field(default=None, exclude_if=is_none)
-
- body: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
-
-class ProviderListResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[ProviderInfo]
-
-
-class ProviderNotFoundErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["ProviderNotFoundError"] = Field(
- default="ProviderNotFoundError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- provider_id: str = Field(validation_alias="providerID", serialization_alias="providerID")
-
- message: str
-
-
-class ProviderRequest(BaseModel):
- settings: ProviderSettings
-
- headers: dict[str, str]
-
- body: dict[str, Any]
-
-
-class ProviderSettings(BaseModel):
- timeout: float | Literal[False] | None = Field(default=None, exclude_if=is_none)
-
- header_timeout: float | Literal[False] | None = Field(
- default=None, validation_alias="headerTimeout", serialization_alias="headerTimeout", exclude_if=is_none
- )
-
- chunk_timeout: float | Literal[False] | None = Field(
- default=None, validation_alias="chunkTimeout", serialization_alias="chunkTimeout", exclude_if=is_none
- )
-
- compaction: ProviderCompaction | None = Field(default=None, exclude_if=is_none)
-
- transport: ProviderTransport | None = Field(default=None, exclude_if=is_none)
-
- encoding: ProviderEncoding | None = Field(default=None, exclude_if=is_none)
-
-
-class ProviderUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["provider.updated"] = "provider.updated"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: Any
-
-
-class Pty(BaseModel):
- id: PtyID
-
- title: str
-
- command: str
-
- args: list[str]
-
- cwd: str
-
- status: OpenEnum[Literal["running", "exited"]]
-
- pid: int
-
- exit_code: int | None = Field(
- default=None, validation_alias="exitCode", serialization_alias="exitCode", exclude_if=is_none
- )
-
-
-class PtyConnectTokenResponse(BaseModel):
- location: LocationPublicRef
-
- data: PtyTicketConnectToken
-
-
-class PtyCreateResponse(BaseModel):
- location: LocationPublicRef
-
- data: Pty
-
-
-class PtyCreatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["pty.created"] = "pty.created"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: PtyCreatedEventData
-
-
-class PtyCreatedEventData(BaseModel):
- info: Pty
-
-
-class PtyDeletedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["pty.deleted"] = "pty.deleted"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: PtyDeletedEventData
-
-
-class PtyDeletedEventData(BaseModel):
- id: PtyID
-
-
-class PtyExitedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["pty.exited"] = "pty.exited"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: PtyExitedEventData
-
-
-class PtyExitedEventData(BaseModel):
- id: PtyID
-
- exit_code: int = Field(validation_alias="exitCode", serialization_alias="exitCode")
-
-
-class PtyGetResponse(BaseModel):
- location: LocationPublicRef
-
- data: Pty
-
-
-class PtyListResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[Pty]
-
-
-class PtyNotFoundErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["PtyNotFoundError"] = Field(
- default="PtyNotFoundError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- pty_id: str = Field(validation_alias="ptyID", serialization_alias="ptyID")
-
- message: str
-
-
-class PtyTicketConnectToken(BaseModel):
- ticket: str
-
- expires_in: int
-
-
-class PtyUpdateResponse(BaseModel):
- location: LocationPublicRef
-
- data: Pty
-
-
-class PtyUpdateSize(BaseModel):
- rows: int
-
- cols: int
-
-
-class PtyUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["pty.updated"] = "pty.updated"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: PtyUpdatedEventData
-
-
-class PtyUpdatedEventData(BaseModel):
- info: Pty
-
-
-class ReferenceGitSource(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["git"] = "git"
-
- repository: str
-
- branch: str | None = Field(default=None, exclude_if=is_none)
-
-
-class ReferenceInfo(BaseModel):
- name: str
-
- path: AbsolutePath
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- hidden: bool | None = Field(default=None, exclude_if=is_none)
-
- source: ReferenceSource
-
-
-class ReferenceListResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[ReferenceInfo]
-
-
-class ReferenceLocalSource(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["local"] = "local"
-
- path: AbsolutePath
-
-
-class ReferenceUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["reference.updated"] = "reference.updated"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: Any
-
-
-class RpcErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["RpcError"] = Field(default="RpcError", validation_alias="_tag", serialization_alias="_tag")
-
- type: str
-
- message: str
-
- data: Any = None
-
-
-class RpcEvent(BaseModel):
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = None
-
- type: str
-
- location: LocationPublicRef
-
- data: dict[str, Any]
-
-
-class RpcInternalErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["RpcInternalError"] = Field(
- default="RpcInternalError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- type: OpenEnum[Literal["rpc.internal", "rpc.invalid_output"]]
-
- message: str
-
- data: Any = None
-
-
-class RpcOutput(BaseModel):
- output: Any = Field(default=None, exclude_if=is_none)
-
-
-class ServerConnectedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- metadata: dict[str, Any] | None = None
-
- location: LocationPublicRef | None = None
-
- type: Literal["server.connected"] = "server.connected"
-
- data: Any
-
-
-class ServerInfo(BaseModel):
- version: str
-
- pid: int
-
- urls: list[str]
-
- paths: ServerInfoPaths
-
- capabilities: ServerInfoCapabilities | None = None
-
-
-class ServerInfoCapabilities(BaseModel):
- persistent_pty: bool | None = Field(
- default=None, validation_alias="persistentPty", serialization_alias="persistentPty"
- )
-
-
-class ServerInfoPaths(BaseModel):
- tmp: str
-
-
-class ServiceUnavailableErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["ServiceUnavailableError"] = Field(
- default="ServiceUnavailableError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- message: str
-
- service: str | None = None
-
-
-class SessionActive(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["running"] = "running"
-
-
-class SessionAgentSelectedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.agent.selected"] = "session.agent.selected"
-
- durable: SessionAgentSelectedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionAgentSelectedEventData
-
-
-class SessionAgentSelectedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- agent: AgentID
-
- previous: AgentID | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionAgentSelectedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionArchivedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.archived"] = "session.archived"
-
- durable: SessionArchivedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionArchivedEventData
-
-
-class SessionArchivedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
-
-class SessionArchivedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionBusyErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["SessionBusyError"] = Field(
- default="SessionBusyError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- session_id: str = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- message: str
-
-
-class SessionCompactionDeltaEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.compaction.delta"] = "session.compaction.delta"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionCompactionDeltaEventData
-
-
-class SessionCompactionDeltaEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- text: str
-
-
-class SessionCompactionEndedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.compaction.ended"] = "session.compaction.ended"
-
- durable: SessionCompactionEndedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionCompactionEndedEventData
-
-
-class SessionCompactionEndedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- reason: OpenEnum[Literal["auto", "manual"]]
-
- model: ModelRef | None = Field(default=None, exclude_if=is_none)
-
- native: SessionMessageNative | None = Field(default=None, exclude_if=is_none)
-
- text: str
-
- cost: MoneyUSD | None = Field(default=None, exclude_if=is_none)
-
- tokens: TokenUsageInfo | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionCompactionEndedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionCompactionFailedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.compaction.failed"] = "session.compaction.failed"
-
- durable: SessionCompactionFailedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionCompactionFailedEventData
-
-
-class SessionCompactionFailedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- reason: OpenEnum[Literal["auto", "manual"]]
-
- error: SessionStructuredError
-
- input_id: SessionMessageID | None = Field(
- default=None, validation_alias="inputID", serialization_alias="inputID", exclude_if=is_none
- )
-
- cost: MoneyUSD | None = Field(default=None, exclude_if=is_none)
-
- tokens: TokenUsageInfo | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionCompactionFailedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionCompactionStartedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.compaction.started"] = "session.compaction.started"
-
- durable: SessionCompactionStartedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionCompactionStartedEventData
-
-
-class SessionCompactionStartedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- reason: OpenEnum[Literal["auto", "manual"]]
-
- input_id: SessionMessageID | None = Field(
- default=None, validation_alias="inputID", serialization_alias="inputID", exclude_if=is_none
- )
-
-
-class SessionCompactionStartedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionCreatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.created"] = "session.created"
-
- durable: SessionCreatedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionCreatedEventData
-
-
-class SessionCreatedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- project_id: ProjectID = Field(validation_alias="projectID", serialization_alias="projectID")
-
- location: LocationRef
-
- subpath: RelativePath | None = Field(default=None, exclude_if=is_none)
-
- parent_id: SessionID | None = Field(
- default=None, validation_alias="parentID", serialization_alias="parentID", exclude_if=is_none
- )
-
- slug: str
-
- title: str | None = Field(default=None, exclude_if=is_none)
-
- agent: AgentID | None = Field(default=None, exclude_if=is_none)
-
- model: ModelRef | None = Field(default=None, exclude_if=is_none)
-
- metadata: SessionMetadata | None = Field(default=None, exclude_if=is_none)
-
- permissions: PermissionRuleset | None = Field(default=None, exclude_if=is_none)
-
- version: str
-
-
-class SessionCreatedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionDeletedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.deleted"] = "session.deleted"
-
- durable: SessionDeletedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionDeletedEventData
-
-
-class SessionDeletedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
-
-class SessionDeletedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion10 = 2
-
-
-class SessionExecutionFailedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.execution.failed"] = "session.execution.failed"
-
- durable: SessionExecutionFailedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionExecutionFailedEventData
-
-
-class SessionExecutionFailedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- error: SessionStructuredError
-
-
-class SessionExecutionFailedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionExecutionInterruptedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.execution.interrupted"] = "session.execution.interrupted"
-
- durable: SessionExecutionInterruptedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionExecutionInterruptedEventData
-
-
-class SessionExecutionInterruptedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- reason: OpenEnum[Literal["user", "shutdown", "superseded", "inactivity"]]
-
-
-class SessionExecutionInterruptedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionExecutionStartedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.execution.started"] = "session.execution.started"
-
- durable: SessionExecutionStartedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionExecutionStartedEventData
-
-
-class SessionExecutionStartedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
-
-class SessionExecutionStartedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionExecutionSucceededEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.execution.succeeded"] = "session.execution.succeeded"
-
- durable: SessionExecutionSucceededEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionExecutionSucceededEventData
-
-
-class SessionExecutionSucceededEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
-
-class SessionExecutionSucceededEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionForkBoundaryBefore(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["before"] = "before"
-
- message_id: SessionMessageID = Field(validation_alias="messageID", serialization_alias="messageID")
-
-
-class SessionForkBoundaryThrough(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["through"] = "through"
-
- message_id: SessionMessageID = Field(validation_alias="messageID", serialization_alias="messageID")
-
-
-class SessionForkedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.forked"] = "session.forked"
-
- durable: SessionForkedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionForkedEventData
-
-
-class SessionForkedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- parent_id: SessionID = Field(validation_alias="parentID", serialization_alias="parentID")
-
- boundary: SessionForkBoundary
-
- instructions: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- instruction_entries: InstructionEntrySnapshot | None = Field(
- default=None,
- validation_alias="instructionEntries",
- serialization_alias="instructionEntries",
- exclude_if=is_none,
- )
-
-
-class SessionForkedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion10 = 2
-
-
-class SessionGenerateResponse(BaseModel):
- data: SessionGenerateResponseData
-
-
-class SessionGenerateResponseData(BaseModel):
- text: str
-
-
-class SessionIdleEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.idle"] = "session.idle"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionIdleEventData
-
-
-class SessionIdleEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
-
-class SessionInboxCancelledEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.inbox.cancelled"] = "session.inbox.cancelled"
-
- durable: SessionInboxCancelledEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionInboxCancelledEventData
-
-
-class SessionInboxCancelledEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- inbox_id: SessionMessageID = Field(validation_alias="inboxID", serialization_alias="inboxID")
-
-
-class SessionInboxCancelledEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionInboxCompaction(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: SessionMessageID
-
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- time: SessionInboxCompactionTime
-
- type: Literal["compaction"] = "compaction"
-
- payload: SessionInboxCompactionPayload
-
- delivery: SessionInboxDelivery
-
-
-class SessionInboxCompactionTime(BaseModel):
- created: float
-
-
-class SessionInboxDeliveredEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.inbox.delivered"] = "session.inbox.delivered"
-
- durable: SessionInboxDeliveredEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionInboxDeliveredEventData
-
-
-class SessionInboxDeliveredEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- inbox_id: SessionMessageID = Field(validation_alias="inboxID", serialization_alias="inboxID")
-
-
-class SessionInboxDeliveredEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionInboxDeliveryChangedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.inbox.delivery.changed"] = "session.inbox.delivery.changed"
-
- durable: SessionInboxDeliveryChangedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionInboxDeliveryChangedEventData
-
-
-class SessionInboxDeliveryChangedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- inbox_id: SessionMessageID = Field(validation_alias="inboxID", serialization_alias="inboxID")
-
- delivery: SessionInboxDelivery
-
-
-class SessionInboxDeliveryChangedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionInboxEnqueuedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.inbox.enqueued"] = "session.inbox.enqueued"
-
- durable: SessionInboxEnqueuedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionInboxEnqueuedEventData
-
-
-class SessionInboxEnqueuedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- inbox_id: SessionMessageID = Field(validation_alias="inboxID", serialization_alias="inboxID")
-
- item: SessionInboxItem
-
-
-class SessionInboxEnqueuedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionInboxItemCompaction(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["compaction"] = "compaction"
-
- payload: SessionInboxCompactionPayload
-
- delivery: SessionInboxDelivery
-
-
-class SessionInboxItemMove(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["move"] = "move"
-
- payload: SessionInboxMovePayload1
-
- delivery: SessionInboxDelivery
-
-
-class SessionInboxItemSynthetic(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["synthetic"] = "synthetic"
-
- payload: SessionInboxSyntheticPayload
-
- delivery: SessionInboxDelivery
-
-
-class SessionInboxItemUser(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["user"] = "user"
-
- payload: SessionInboxUserPayload
-
- delivery: SessionInboxDelivery
-
-
-class SessionInboxMove(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: SessionMessageID
-
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- time: SessionInboxMoveTime
-
- type: Literal["move"] = "move"
-
- delivery: SessionInboxDelivery
-
- payload: SessionInboxMovePayload
-
-
-class SessionInboxMovePayload(BaseModel):
- project_id: ProjectID = Field(validation_alias="projectID", serialization_alias="projectID")
-
- subpath: RelativePath | None = Field(default=None, exclude_if=is_none)
-
- location: LocationPublicRef
-
-
-class SessionInboxMovePayload1(BaseModel):
- location: LocationRef
-
- project_id: ProjectID = Field(validation_alias="projectID", serialization_alias="projectID")
-
- subpath: RelativePath | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionInboxMoveTime(BaseModel):
- created: float
-
-
-class SessionInboxSynthetic(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: SessionMessageID
-
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- time: SessionInboxSyntheticTime
-
- type: Literal["synthetic"] = "synthetic"
-
- payload: SessionInboxSyntheticPayload
-
- delivery: SessionInboxDelivery
-
-
-class SessionInboxSyntheticPayload(BaseModel):
- text: str
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionInboxSyntheticTime(BaseModel):
- created: float
-
-
-class SessionInboxUser(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: SessionMessageID
-
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- time: SessionInboxUserTime
-
- type: Literal["user"] = "user"
-
- payload: SessionInboxUserPayload
-
- delivery: SessionInboxDelivery
-
-
-class SessionInboxUserPayload(BaseModel):
- text: str
-
- files: list[PromptFileAttachment] | None = Field(default=None, exclude_if=is_none)
-
- agents: list[PromptAgentAttachment] | None = Field(default=None, exclude_if=is_none)
-
- skills: list[PromptSkillAttachment] | None = Field(default=None, exclude_if=is_none)
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionInboxUserTime(BaseModel):
- created: float
-
-
-class SessionInfo(BaseModel):
- id: SessionID
-
- parent_id: SessionID | None = Field(
- default=None, validation_alias="parentID", serialization_alias="parentID", exclude_if=is_none
- )
-
- fork: SessionInfoFork | None = Field(default=None, exclude_if=is_none)
-
- project_id: ProjectID = Field(validation_alias="projectID", serialization_alias="projectID")
-
- agent: AgentID | None = Field(default=None, exclude_if=is_none)
-
- model: ModelRef | None = Field(default=None, exclude_if=is_none)
-
- cost: MoneyUSD
-
- tokens: TokenUsageInfo
-
- outcome: OpenEnum[Literal["succeeded", "failed", "interrupted"]] | None = Field(default=None, exclude_if=is_none)
-
- time: SessionInfoTime
-
- title: str | None = Field(default=None, exclude_if=is_none)
-
- subpath: RelativePath | None = Field(default=None, exclude_if=is_none)
-
- metadata: SessionMetadata | None = Field(default=None, exclude_if=is_none)
-
- permissions: PermissionRuleset | None = Field(default=None, exclude_if=is_none)
-
- revert: SessionRevert | None = Field(default=None, exclude_if=is_none)
-
- location: LocationPublicRef
-
-
-class SessionInfoFork(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- boundary: SessionForkBoundary
-
-
-class SessionInfoTime(BaseModel):
- created: float
-
- updated: float
-
- idle: float | None = Field(default=None, exclude_if=is_none)
-
- viewed: float | None = Field(default=None, exclude_if=is_none)
-
- archived: float | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionInstructionsUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.instructions.updated"] = "session.instructions.updated"
-
- durable: SessionInstructionsUpdatedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionInstructionsUpdatedEventData
-
-
-class SessionInstructionsUpdatedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- delta: dict[str, InstructionHash | Literal["removed"]]
-
- text: str | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionInstructionsUpdatedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion10 = 2
-
-
-class SessionInterruptResponse(BaseModel):
- interrupted: bool
- """Whether an active execution owned by this OpenCode process was interrupted."""
-
-
-class SessionMessageAgentSelected(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: SessionMessageID
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- time: SessionMessageAgentSelectedTime
-
- type: Literal["agent-switched"] = "agent-switched"
-
- agent: AgentID
-
- previous: AgentID | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMessageAgentSelectedTime(BaseModel):
- created: float
-
-
-class SessionMessageAssistant(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: SessionMessageID
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- time: SessionMessageAssistantTime
-
- type: Literal["assistant"] = "assistant"
-
- agent: AgentID
-
- model: ModelRef
-
- content: list[SessionMessageAssistantContentItem]
-
- snapshot: SessionMessageAssistantSnapshot | None = Field(default=None, exclude_if=is_none)
-
- finish: OpenEnum[Literal["stop", "length", "tool-calls", "content-filter", "error", "unknown"]] | None = Field(
- default=None, exclude_if=is_none
- )
-
- raw_finish: str | None = Field(
- default=None, validation_alias="rawFinish", serialization_alias="rawFinish", exclude_if=is_none
- )
-
- native: SessionMessageNative | None = Field(default=None, exclude_if=is_none)
-
- cost: MoneyUSD | None = Field(default=None, exclude_if=is_none)
-
- tokens: TokenUsageInfo | None = Field(default=None, exclude_if=is_none)
-
- error: SessionStructuredError | None = Field(default=None, exclude_if=is_none)
-
- retry: SessionMessageAssistantRetry | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMessageAssistantReasoning(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["reasoning"] = "reasoning"
-
- text: str
-
- native: SessionMessageNative | None = Field(default=None, exclude_if=is_none)
-
- time: SessionMessageAssistantReasoningTime | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMessageAssistantReasoningTime(BaseModel):
- created: float
-
- completed: float | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMessageAssistantRetry(BaseModel):
- attempt: int
-
- at: float
-
- error: SessionStructuredError
-
-
-class SessionMessageAssistantSnapshot(BaseModel):
- start: SnapshotID | None = Field(default=None, exclude_if=is_none)
-
- end: SnapshotID | None = Field(default=None, exclude_if=is_none)
-
- files: list[RelativePath] | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMessageAssistantText(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["text"] = "text"
-
- text: str
-
- native: SessionMessageNative | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMessageAssistantTime(BaseModel):
- created: float
-
- streamed: float | None = Field(default=None, exclude_if=is_none)
-
- completed: float | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMessageAssistantTool(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["tool"] = "tool"
-
- id: str
-
- name: str
-
- executed: bool | None = Field(default=None, exclude_if=is_none)
-
- native: SessionMessageNative | None = Field(default=None, exclude_if=is_none)
-
- state: SessionMessageAssistantToolState
-
- time: SessionMessageAssistantToolTime
-
-
-class SessionMessageAssistantToolTime(BaseModel):
- created: float
-
- ran: float | None = Field(default=None, exclude_if=is_none)
-
- completed: float | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMessageCompactionCompleted(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type", "status")
-
- type: Literal["compaction"] = "compaction"
-
- id: SessionMessageID
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- time: SessionMessageCompactionCompletedTime
-
- status: Literal["completed"] = "completed"
-
- reason: OpenEnum[Literal["auto", "manual"]]
-
- model: ModelRef | None = Field(default=None, exclude_if=is_none)
-
- native: SessionMessageNative | None = Field(default=None, exclude_if=is_none)
-
- summary: str
-
- cost: MoneyUSD | None = Field(default=None, exclude_if=is_none)
-
- tokens: TokenUsageInfo | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMessageCompactionCompletedTime(BaseModel):
- created: float
-
-
-class SessionMessageCompactionFailed(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type", "status")
-
- type: Literal["compaction"] = "compaction"
-
- id: SessionMessageID
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- time: SessionMessageCompactionFailedTime
-
- status: Literal["failed"] = "failed"
-
- reason: OpenEnum[Literal["auto", "manual"]]
-
- error: SessionStructuredError
-
- cost: MoneyUSD | None = Field(default=None, exclude_if=is_none)
-
- tokens: TokenUsageInfo | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMessageCompactionFailedTime(BaseModel):
- created: float
-
-
-class SessionMessageCompactionRunning(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type", "status")
-
- type: Literal["compaction"] = "compaction"
-
- id: SessionMessageID
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- time: SessionMessageCompactionRunningTime
-
- status: Literal["running"] = "running"
-
- reason: OpenEnum[Literal["auto", "manual"]]
-
- summary: str
-
-
-class SessionMessageCompactionRunningTime(BaseModel):
- created: float
-
-
-class SessionMessageContentUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.message.content.updated"] = "session.message.content.updated"
-
- durable: SessionMessageContentUpdatedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionMessageContentUpdatedEventData
-
-
-class SessionMessageContentUpdatedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- message_id: SessionMessageID = Field(validation_alias="messageID", serialization_alias="messageID")
-
- content: list[SessionMessageAssistantContent]
-
-
-class SessionMessageContentUpdatedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionMessageIdle(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: SessionMessageID
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- time: SessionMessageIdleTime
-
- type: Literal["idle"] = "idle"
-
- outcome: OpenEnum[Literal["succeeded", "failed", "interrupted"]]
-
- error: SessionStructuredError | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMessageIdleTime(BaseModel):
- created: float
-
-
-class SessionMessageLocationSwitched(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: SessionMessageID
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- time: SessionMessageLocationSwitchedTime
-
- type: Literal["location-switched"] = "location-switched"
-
- project_id: ProjectID | None = Field(
- default=None, validation_alias="projectID", serialization_alias="projectID", exclude_if=is_none
- )
-
- subpath: RelativePath | None = Field(default=None, exclude_if=is_none)
-
- location: LocationPublicRef
-
- previous: SessionMessageLocationSwitchedPrevious | None = None
-
-
-class SessionMessageLocationSwitchedPrevious(BaseModel):
- location: LocationPublicRef
-
- project_id: ProjectID | None = Field(
- default=None, validation_alias="projectID", serialization_alias="projectID", exclude_if=is_none
- )
-
- subpath: RelativePath | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMessageLocationSwitchedTime(BaseModel):
- created: float
-
-
-class SessionMessageModelSelected(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: SessionMessageID
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- time: SessionMessageModelSelectedTime
-
- type: Literal["model-switched"] = "model-switched"
-
- model: ModelRef
-
- previous: ModelRef | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMessageModelSelectedTime(BaseModel):
- created: float
-
-
-class SessionMessageShell(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: SessionMessageID
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- time: SessionMessageShellTime
-
- type: Literal["shell"] = "shell"
-
- shell_id: ShellID = Field(validation_alias="shellID", serialization_alias="shellID")
-
- command: str
-
- status: OpenEnum[Literal["running", "exited", "timeout", "killed"]]
-
- exit: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]] | None = Field(default=None, exclude_if=is_none)
-
- output: SessionMessageShellOutput | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMessageShellOutput(BaseModel):
- output: str
-
- cursor: int
-
- size: int
-
- truncated: bool
-
-
-class SessionMessageShellTime(BaseModel):
- created: float
-
- completed: float | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMessageSkill(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: SessionMessageID
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- time: SessionMessageSkillTime
-
- type: Literal["skill"] = "skill"
-
- skill: SkillID
-
- name: SkillName
-
- text: str
-
-
-class SessionMessageSkillTime(BaseModel):
- created: float
-
-
-class SessionMessageSynthetic(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: SessionMessageID
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- time: SessionMessageSyntheticTime
-
- text: str
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["synthetic"] = "synthetic"
-
-
-class SessionMessageSyntheticTime(BaseModel):
- created: float
-
-
-class SessionMessageSystem(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: SessionMessageID
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- time: SessionMessageSystemTime
-
- type: Literal["system"] = "system"
-
- text: str
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMessageSystemTime(BaseModel):
- created: float
-
-
-class SessionMessageToolStateCompleted(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["completed"] = "completed"
-
- input: dict[str, Any]
-
- content: list[ToolContent]
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMessageToolStateError(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["error"] = "error"
-
- input: dict[str, Any]
-
- error: SessionStructuredError
-
- content: list[ToolContent] | None = Field(default=None, exclude_if=is_none)
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMessageToolStateRunning(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["running"] = "running"
-
- input: dict[str, Any]
-
- metadata: dict[str, Any]
-
-
-class SessionMessageToolStateStreaming(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("status",)
-
- status: Literal["streaming"] = "streaming"
-
- input: str
-
-
-class SessionMessageUser(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: SessionMessageID
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- time: SessionMessageUserTime
-
- text: str
-
- files: list[PromptFileAttachment] | None = Field(default=None, exclude_if=is_none)
-
- agents: list[PromptAgentAttachment] | None = Field(default=None, exclude_if=is_none)
-
- skills: list[PromptSkillAttachment] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["user"] = "user"
-
-
-class SessionMessageUserTime(BaseModel):
- created: float
-
-
-class SessionMessagesResponse(BaseModel):
- data: list[SessionMessageInfo]
-
- cursor: SessionMessagesResponseCursor
-
-
-class SessionMessagesResponseCursor(BaseModel):
- previous: str | None = None
-
- next: str | None = None
-
-
-class SessionMetadataUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.metadata.updated"] = "session.metadata.updated"
-
- durable: SessionMetadataUpdatedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionMetadataUpdatedEventData
-
-
-class SessionMetadataUpdatedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- metadata: SessionMetadata
-
-
-class SessionMetadataUpdatedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionModelSelectedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.model.selected"] = "session.model.selected"
-
- durable: SessionModelSelectedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionModelSelectedEventData
-
-
-class SessionModelSelectedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- model: ModelRef
-
- previous: ModelRef | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionModelSelectedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionMovedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.moved"] = "session.moved"
-
- durable: SessionMovedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionMovedEventData
-
-
-class SessionMovedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- location: LocationRef
-
- project_id: ProjectID = Field(validation_alias="projectID", serialization_alias="projectID")
-
- subpath: RelativePath | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionMovedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionNotFoundErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["SessionNotFoundError"] = Field(
- default="SessionNotFoundError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- session_id: str = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- message: str
-
-
-class SessionPermissionsEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.permissions"] = "session.permissions"
-
- durable: SessionPermissionsEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionPermissionsEventData
-
-
-class SessionPermissionsEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- permissions: PermissionRuleset
-
-
-class SessionPermissionsEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionReasoningDeltaEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.reasoning.delta"] = "session.reasoning.delta"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionReasoningDeltaEventData
-
-
-class SessionReasoningDeltaEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- assistant_message_id: SessionMessageID = Field(
- validation_alias="assistantMessageID", serialization_alias="assistantMessageID"
- )
-
- ordinal: int
-
- delta: str
-
-
-class SessionReasoningEndedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.reasoning.ended"] = "session.reasoning.ended"
-
- durable: SessionReasoningEndedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionReasoningEndedEventData
-
-
-class SessionReasoningEndedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- assistant_message_id: SessionMessageID = Field(
- validation_alias="assistantMessageID", serialization_alias="assistantMessageID"
- )
-
- ordinal: int
-
- text: str
-
- native: SessionMessageNative | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionReasoningEndedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionReasoningStartedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.reasoning.started"] = "session.reasoning.started"
-
- durable: SessionReasoningStartedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionReasoningStartedEventData
-
-
-class SessionReasoningStartedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- assistant_message_id: SessionMessageID = Field(
- validation_alias="assistantMessageID", serialization_alias="assistantMessageID"
- )
-
- ordinal: int
-
- native: SessionMessageNative | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionReasoningStartedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionRenamedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.renamed"] = "session.renamed"
-
- durable: SessionRenamedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionRenamedEventData
-
-
-class SessionRenamedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- title: str
-
-
-class SessionRenamedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionRetryScheduledEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.retry.scheduled"] = "session.retry.scheduled"
-
- durable: SessionRetryScheduledEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionRetryScheduledEventData
-
-
-class SessionRetryScheduledEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- assistant_message_id: SessionMessageID = Field(
- validation_alias="assistantMessageID", serialization_alias="assistantMessageID"
- )
-
- attempt: int
-
- at: int
-
- error: SessionStructuredError
-
-
-class SessionRetryScheduledEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionRevert(BaseModel):
- message_id: SessionMessageID = Field(validation_alias="messageID", serialization_alias="messageID")
-
- part_id: str | None = Field(
- default=None, validation_alias="partID", serialization_alias="partID", exclude_if=is_none
- )
-
- snapshot: SnapshotID | None = Field(default=None, exclude_if=is_none)
-
- files: list[FileDiffInfo] | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionRevertClearedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.revert.cleared"] = "session.revert.cleared"
-
- durable: SessionRevertClearedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionRevertClearedEventData
-
-
-class SessionRevertClearedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
-
-class SessionRevertClearedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionRevertCommittedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.revert.committed"] = "session.revert.committed"
-
- durable: SessionRevertCommittedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionRevertCommittedEventData
-
-
-class SessionRevertCommittedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- to: SessionMessageID
-
-
-class SessionRevertCommittedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionRevertStagedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.revert.staged"] = "session.revert.staged"
-
- durable: SessionRevertStagedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionRevertStagedEventData
-
-
-class SessionRevertStagedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- revert: SessionRevert
-
-
-class SessionRevertStagedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionShellEndedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.shell.ended"] = "session.shell.ended"
-
- durable: SessionShellEndedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionShellEndedEventData
-
-
-class SessionShellEndedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- shell: ShellInfo
-
- output: SessionShellEndedEventDataOutput
-
-
-class SessionShellEndedEventDataOutput(BaseModel):
- output: str
-
- cursor: int
-
- size: int
-
- truncated: bool
-
-
-class SessionShellEndedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionShellStartedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.shell.started"] = "session.shell.started"
-
- durable: SessionShellStartedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionShellStartedEventData
-
-
-class SessionShellStartedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- shell: ShellInfo
-
-
-class SessionShellStartedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionSkillActivatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.skill.activated"] = "session.skill.activated"
-
- durable: SessionSkillActivatedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionSkillActivatedEventData
-
-
-class SessionSkillActivatedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- id: SkillID
-
- name: SkillName
-
- text: str
-
-
-class SessionSkillActivatedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionStatsActivity(BaseModel):
- date: str
-
- steps: int
-
-
-class SessionStatsInfo(BaseModel):
- range: SessionStatsInfoRange
-
- sessions: int
-
- subagents: int
-
- prompts: int
-
- steps: int
-
- tokens: TokenUsageInfo
-
- cost: MoneyUSD
-
- tools: SessionStatsTools
-
- active_days: int = Field(validation_alias="activeDays", serialization_alias="activeDays")
-
- streak: int
-
- activity: list[SessionStatsActivity]
-
- models: list[SessionStatsModelUsage]
-
-
-class SessionStatsInfoRange(BaseModel):
- from_: float = Field(validation_alias="from", serialization_alias="from")
-
- to: float
-
-
-class SessionStatsModelUsage(BaseModel):
- model: ModelRef
-
- steps: int
-
- tokens: TokenUsageInfo
-
- cost: MoneyUSD
-
-
-class SessionStatsToolTotals(BaseModel):
- calls: int
-
- succeeded: int
-
- failed: int
-
- unfinished: int
-
-
-class SessionStatsToolUsage(BaseModel):
- name: str
-
- calls: int
-
- succeeded: int
-
- failed: int
-
- unfinished: int
-
- duration_p50: float | None = Field(
- default=None, validation_alias="durationP50", serialization_alias="durationP50", exclude_if=is_none
- )
-
-
-class SessionStatsToolsDetail(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("mode",)
-
- mode: Literal["detail"] = "detail"
-
- totals: SessionStatsToolTotals
-
- usage: list[SessionStatsToolUsage]
-
-
-class SessionStatsToolsNone(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("mode",)
-
- mode: Literal["none"] = "none"
-
-
-class SessionStatsToolsSummary(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("mode",)
-
- mode: Literal["summary"] = "summary"
-
- totals: SessionStatsToolTotals
-
-
-class SessionStatusBusy(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["busy"] = "busy"
-
-
-class SessionStatusIdle(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["idle"] = "idle"
-
-
-class SessionStatusRetry(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["retry"] = "retry"
-
- attempt: int
-
- message: str
-
- action: SessionStatusRetryAction | None = Field(default=None, exclude_if=is_none)
-
- next: int
-
-
-class SessionStatusRetryAction(BaseModel):
- reason: str
-
- provider: str
-
- title: str
-
- message: str
-
- label: str
-
- link: str | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionStatusUpdated(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.status"] = "session.status"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionStatusUpdatedData
-
-
-class SessionStatusUpdatedData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- status: SessionStatus
-
-
-class SessionStepEndedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.step.ended"] = "session.step.ended"
-
- durable: SessionStepEndedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionStepEndedEventData
-
-
-class SessionStepEndedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- assistant_message_id: SessionMessageID = Field(
- validation_alias="assistantMessageID", serialization_alias="assistantMessageID"
- )
-
- finish: OpenEnum[Literal["stop", "length", "tool-calls", "content-filter", "error", "unknown"]]
-
- raw_finish: str | None = Field(
- default=None, validation_alias="rawFinish", serialization_alias="rawFinish", exclude_if=is_none
- )
-
- native: SessionMessageNative | None = Field(default=None, exclude_if=is_none)
-
- cost: MoneyUSD
-
- tokens: TokenUsageInfo
-
- snapshot: SnapshotID | None = Field(default=None, exclude_if=is_none)
-
- files: list[RelativePath] | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionStepEndedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionStepFailedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.step.failed"] = "session.step.failed"
-
- durable: SessionStepFailedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionStepFailedEventData
-
-
-class SessionStepFailedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- assistant_message_id: SessionMessageID = Field(
- validation_alias="assistantMessageID", serialization_alias="assistantMessageID"
- )
-
- error: SessionStructuredError
-
- finish: Literal["content-filter"] | None = Field(default=None, exclude_if=is_none)
-
- raw_finish: str | None = Field(
- default=None, validation_alias="rawFinish", serialization_alias="rawFinish", exclude_if=is_none
- )
-
- native: SessionMessageNative | None = Field(default=None, exclude_if=is_none)
-
- cost: MoneyUSD | None = Field(default=None, exclude_if=is_none)
-
- tokens: TokenUsageInfo | None = Field(default=None, exclude_if=is_none)
-
- snapshot: SnapshotID | None = Field(default=None, exclude_if=is_none)
-
- files: list[RelativePath] | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionStepFailedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionStepStartedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.step.started"] = "session.step.started"
-
- durable: SessionStepStartedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionStepStartedEventData
-
-
-class SessionStepStartedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- assistant_message_id: SessionMessageID = Field(
- validation_alias="assistantMessageID", serialization_alias="assistantMessageID"
- )
-
- agent: AgentID
-
- model: ModelRef
-
- snapshot: SnapshotID | None = Field(default=None, exclude_if=is_none)
-
- started: int
-
-
-class SessionStepStartedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionStepStreamedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.step.streamed"] = "session.step.streamed"
-
- durable: SessionStepStreamedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionStepStreamedEventData
-
-
-class SessionStepStreamedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- assistant_message_id: SessionMessageID = Field(
- validation_alias="assistantMessageID", serialization_alias="assistantMessageID"
- )
-
-
-class SessionStepStreamedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionStructuredError(BaseModel):
- type: str
-
- message: str
-
- status: int | None = Field(default=None, exclude_if=is_none)
-
- response: SessionStructuredErrorResponse | None = Field(default=None, exclude_if=is_none)
-
- retry_after_ms: int | None = Field(
- default=None, validation_alias="retryAfterMs", serialization_alias="retryAfterMs", exclude_if=is_none
- )
-
-
-class SessionStructuredErrorResponse(BaseModel):
- body: str
-
-
-class SessionSyntheticEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.synthetic"] = "session.synthetic"
-
- durable: SessionSyntheticEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionSyntheticEventData
-
-
-class SessionSyntheticEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- text: str
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionSyntheticEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionTextDeltaEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.text.delta"] = "session.text.delta"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionTextDeltaEventData
-
-
-class SessionTextDeltaEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- assistant_message_id: SessionMessageID = Field(
- validation_alias="assistantMessageID", serialization_alias="assistantMessageID"
- )
-
- ordinal: int
-
- delta: str
-
-
-class SessionTextEndedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.text.ended"] = "session.text.ended"
-
- durable: SessionTextEndedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionTextEndedEventData
-
-
-class SessionTextEndedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- assistant_message_id: SessionMessageID = Field(
- validation_alias="assistantMessageID", serialization_alias="assistantMessageID"
- )
-
- ordinal: int
-
- text: str
-
- native: SessionMessageNative | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionTextEndedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionTextStartedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.text.started"] = "session.text.started"
-
- durable: SessionTextStartedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionTextStartedEventData
-
-
-class SessionTextStartedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- assistant_message_id: SessionMessageID = Field(
- validation_alias="assistantMessageID", serialization_alias="assistantMessageID"
- )
-
- ordinal: int
-
-
-class SessionTextStartedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionToolCalledEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.tool.called"] = "session.tool.called"
-
- durable: SessionToolCalledEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionToolCalledEventData
-
-
-class SessionToolCalledEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- assistant_message_id: SessionMessageID = Field(
- validation_alias="assistantMessageID", serialization_alias="assistantMessageID"
- )
-
- id: str
-
- input: dict[str, Any]
-
- executed: bool
-
- native: SessionMessageNative | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionToolCalledEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionToolFailedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.tool.failed"] = "session.tool.failed"
-
- durable: SessionToolFailedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionToolFailedEventData
-
-
-class SessionToolFailedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- assistant_message_id: SessionMessageID = Field(
- validation_alias="assistantMessageID", serialization_alias="assistantMessageID"
- )
-
- id: str
-
- error: SessionStructuredError
-
- content: list[ToolContent] | None = Field(default=None, exclude_if=is_none)
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- executed: bool
-
- native: SessionMessageNative | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionToolFailedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion10 = 2
-
-
-class SessionToolInputDeltaEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.tool.input.delta"] = "session.tool.input.delta"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionToolInputDeltaEventData
-
-
-class SessionToolInputDeltaEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- assistant_message_id: SessionMessageID = Field(
- validation_alias="assistantMessageID", serialization_alias="assistantMessageID"
- )
-
- id: str
-
- delta: str
-
-
-class SessionToolInputEndedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.tool.input.ended"] = "session.tool.input.ended"
-
- durable: SessionToolInputEndedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionToolInputEndedEventData
-
-
-class SessionToolInputEndedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- assistant_message_id: SessionMessageID = Field(
- validation_alias="assistantMessageID", serialization_alias="assistantMessageID"
- )
-
- id: str
-
- text: str
-
-
-class SessionToolInputEndedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionToolInputStartedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.tool.input.started"] = "session.tool.input.started"
-
- durable: SessionToolInputStartedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionToolInputStartedEventData
-
-
-class SessionToolInputStartedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- assistant_message_id: SessionMessageID = Field(
- validation_alias="assistantMessageID", serialization_alias="assistantMessageID"
- )
-
- id: str
-
- name: str
-
-
-class SessionToolInputStartedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionToolProgressEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.tool.progress"] = "session.tool.progress"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionToolProgressEventData
-
-
-class SessionToolProgressEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- assistant_message_id: SessionMessageID = Field(
- validation_alias="assistantMessageID", serialization_alias="assistantMessageID"
- )
-
- id: str
-
- metadata: dict[str, Any]
-
-
-class SessionToolSuccessEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.tool.success"] = "session.tool.success"
-
- durable: SessionToolSuccessEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionToolSuccessEventData
-
-
-class SessionToolSuccessEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- assistant_message_id: SessionMessageID = Field(
- validation_alias="assistantMessageID", serialization_alias="assistantMessageID"
- )
-
- id: str
-
- content: list[ToolContent]
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- executed: bool
-
- native: SessionMessageNative | None = Field(default=None, exclude_if=is_none)
-
-
-class SessionToolSuccessEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion10 = 2
-
-
-class SessionTransferData(BaseModel):
- info: SessionInfo
-
- messages: list[SessionMessageInfo]
-
-
-class SessionUnarchivedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.unarchived"] = "session.unarchived"
-
- durable: SessionUnarchivedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionUnarchivedEventData
-
-
-class SessionUnarchivedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
-
-class SessionUnarchivedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionUsageRecordedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.usage.recorded"] = "session.usage.recorded"
-
- durable: SessionUsageRecordedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionUsageRecordedEventData
-
-
-class SessionUsageRecordedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- source: OpenEnum[Literal["title", "compaction"]]
-
- cost: MoneyUSD
-
- tokens: TokenUsageInfo
-
-
-class SessionUsageRecordedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionUsageUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.usage.updated"] = "session.usage.updated"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionUsageUpdatedEventData
-
-
-class SessionUsageUpdatedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- cost: MoneyUSD
-
- tokens: TokenUsageInfo
-
-
-class SessionViewedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["session.viewed"] = "session.viewed"
-
- durable: SessionViewedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: SessionViewedEventData
-
-
-class SessionViewedEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
- idle: float
-
-
-class SessionViewedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class SessionsResponse(BaseModel):
- data: list[SessionInfo]
-
- cursor: SessionsResponseCursor
-
-
-class SessionsResponseCursor(BaseModel):
- previous: SessionsCursor | None = None
-
- next: SessionsCursor | None = None
-
-
-class ShellCreateResponse(BaseModel):
- location: LocationPublicRef
-
- data: ShellInfo
-
-
-class ShellCreatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["shell.created"] = "shell.created"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: ShellCreatedEventData
-
-
-class ShellCreatedEventData(BaseModel):
- info: ShellInfo
-
-
-class ShellDeletedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["shell.deleted"] = "shell.deleted"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: ShellDeletedEventData
-
-
-class ShellDeletedEventData(BaseModel):
- id: ShellID
-
-
-class ShellExitedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["shell.exited"] = "shell.exited"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: ShellExitedEventData
-
-
-class ShellExitedEventData(BaseModel):
- id: ShellID
-
- exit: float | None = Field(default=None, exclude_if=is_none)
-
- status: OpenEnum[Literal["running", "exited", "timeout", "killed"]]
-
-
-class ShellGetResponse(BaseModel):
- location: LocationPublicRef
-
- data: ShellInfo
-
-
-class ShellInfo(BaseModel):
- id: ShellID
-
- status: OpenEnum[Literal["running", "exited", "timeout", "killed"]]
-
- command: str
-
- cwd: str
-
- shell: str
-
- file: str
-
- pid: int | None = Field(default=None, exclude_if=is_none)
-
- exit: float | None = Field(default=None, exclude_if=is_none)
-
- signal: str | None = Field(default=None, exclude_if=is_none)
-
- metadata: dict[str, Any]
-
- time: ShellInfoTime
-
-
-class ShellInfoTime(BaseModel):
- started: float
- """Start time in milliseconds since the Unix epoch"""
-
- completed: float | None = Field(default=None, exclude_if=is_none)
-
-
-class ShellListResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[ShellInfo]
-
-
-class ShellNotFoundErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["ShellNotFoundError"] = Field(
- default="ShellNotFoundError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- id: str
-
- message: str
-
-
-class ShellOutputResponse(BaseModel):
- location: LocationPublicRef
-
- data: ShellOutputResponseData
-
-
-class ShellOutputResponseData(BaseModel):
- output: str
-
- cursor: int
-
- size: int
-
- truncated: bool
-
-
-class SkillInfo(BaseModel):
- id: SkillID
-
- name: SkillName
-
- description: str | None = Field(default=None, exclude_if=is_none)
-
- autoinvoke: bool | None = Field(default=None, exclude_if=is_none)
-
- path: AbsolutePath
-
- content: str
-
-
-class SkillListResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[SkillInfo]
-
-
-class SkillNotFoundErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["SkillNotFoundError"] = Field(
- default="SkillNotFoundError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- skill: SkillID
-
- message: str
-
-
-class SkillUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["skill.updated"] = "skill.updated"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: Any
-
-
-class TokenUsageInfo(BaseModel):
- input: float
-
- output: float
-
- reasoning: float
-
- cache: TokenUsageInfoCache
-
-
-class TokenUsageInfoCache(BaseModel):
- read: float
-
- write: float
-
-
-class ToolFileContent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["file"] = "file"
-
- uri: str
-
- mime: str
-
- name: str | None = None
-
-
-class ToolTextContent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["text"] = "text"
-
- text: str
-
-
-class TuiCommandExecuteEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["tui.command.execute"] = "tui.command.execute"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: TuiCommandExecuteEventData
-
-
-class TuiCommandExecuteEventData(BaseModel):
- command: (
- OpenEnum[
- Literal[
- "session.list",
- "session.new",
- "session.interrupt",
- "session.background",
- "session.compact",
- "session.page.up",
- "session.page.down",
- "session.line.up",
- "session.line.down",
- "session.half.page.up",
- "session.half.page.down",
- "session.first",
- "session.last",
- "prompt.clear",
- "prompt.submit",
- "agent.cycle",
- ]
- ]
- | str
- )
-
-
-class TuiPromptAppendEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["tui.prompt.append"] = "tui.prompt.append"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: TuiPromptAppendEventData
-
-
-class TuiPromptAppendEventData(BaseModel):
- text: str
-
-
-class TuiSessionSelectEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["tui.session.select"] = "tui.session.select"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: TuiSessionSelectEventData
-
-
-class TuiSessionSelectEventData(BaseModel):
- session_id: SessionID = Field(validation_alias="sessionID", serialization_alias="sessionID")
-
-
-class TuiToastShowEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["tui.toast.show"] = "tui.toast.show"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: TuiToastShowEventData
-
-
-class TuiToastShowEventData(BaseModel):
- title: str | None = Field(default=None, exclude_if=is_none)
-
- message: str
-
- variant: OpenEnum[Literal["info", "success", "warning", "error"]]
-
- duration: int | None = None
-
-
-class UnauthorizedErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["UnauthorizedError"] = Field(
- default="UnauthorizedError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- message: str
-
-
-class UnknownConfigEntry(BaseModel):
- """A `ConfigEntry` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
- path: AbsolutePath | None = None
-
-
-class UnknownConfigInfoMcpServersValue(BaseModel):
- """A `ConfigInfoMcpServersValue` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
- disabled: bool | None = None
-
- codemode: bool | None = None
-
- protocol: McpProtocol | None = None
-
-
-class UnknownConfigModelReasoning(BaseModel):
- """A `ConfigModelReasoning` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- supported: Any = None
-
-
-class UnknownConnectionInfo(BaseModel):
- """A `ConnectionInfo` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
- status: ConnectionStatus | None = None
-
-
-class UnknownCredentialValue(BaseModel):
- """A `CredentialValue` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
- metadata: dict[str, Any] | None = None
-
-
-class UnknownErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["UnknownError"] = Field(default="UnknownError", validation_alias="_tag", serialization_alias="_tag")
-
- message: str
-
- ref: str | None = None
-
-
-class UnknownEvent(BaseModel):
- """A `Event` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
- id: EventID | None = None
-
- metadata: dict[str, Any] | None = None
-
-
-class UnknownFormField(BaseModel):
- """A `FormField` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
- key: str | None = None
-
- title: str | None = None
-
- description: str | None = None
-
-
-class UnknownFormState(BaseModel):
- """A `FormState` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- status: Any = None
-
-
-class UnknownIntegrationAttemptStatus(BaseModel):
- """A `IntegrationAttemptStatus` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- status: Any = None
-
-
-class UnknownIntegrationCommandAttemptStatus(BaseModel):
- """A `IntegrationCommandAttemptStatus` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- status: Any = None
-
-
-class UnknownIntegrationMethod(BaseModel):
- """A `IntegrationMethod` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
-
-class UnknownMcpAddConfig(BaseModel):
- """A `McpAddConfig` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
- disabled: bool | None = None
-
- codemode: bool | None = None
-
- protocol: McpProtocol | None = None
-
-
-class UnknownMcpServerStatus(BaseModel):
- """A `McpServerStatus` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- status: Any = None
-
-
-class UnknownPluginSource(BaseModel):
- """A `PluginSource` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
-
-class UnknownPluginState(BaseModel):
- """A `PluginState` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- status: Any = None
-
-
-class UnknownPromptFileSource(BaseModel):
- """A `PromptFileSource` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
-
-class UnknownProviderCompaction(BaseModel):
- """A `ProviderCompaction` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
-
-class UnknownReferenceSource(BaseModel):
- """A `ReferenceSource` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
-
-class UnknownSessionEventDurable(BaseModel):
- """A `SessionEventDurable` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
- id: EventID | None = None
-
- created: float | None = None
-
- metadata: dict[str, Any] | None = None
-
- location: LocationRef | None = None
-
-
-class UnknownSessionForkBoundary(BaseModel):
- """A `SessionForkBoundary` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
- message_id: SessionMessageID | None = Field(
- default=None, validation_alias="messageID", serialization_alias="messageID"
- )
-
-
-class UnknownSessionInboxInfo(BaseModel):
- """A `SessionInboxInfo` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
- id: SessionMessageID | None = None
-
- session_id: SessionID | None = Field(default=None, validation_alias="sessionID", serialization_alias="sessionID")
-
- delivery: SessionInboxDelivery | None = None
-
-
-class UnknownSessionInboxItem(BaseModel):
- """A `SessionInboxItem` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
- delivery: SessionInboxDelivery | None = None
-
-
-class UnknownSessionMessageAssistantContent(BaseModel):
- """A `SessionMessageAssistantContent` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
- native: SessionMessageNative | None = None
-
-
-class UnknownSessionMessageAssistantContentItem(BaseModel):
- """A `SessionMessageAssistantContentItem` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
- native: SessionMessageNative | None = None
-
-
-class UnknownSessionMessageAssistantToolState(BaseModel):
- """A `SessionMessageAssistantToolState` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- status: Any = None
-
-
-class UnknownSessionMessageCompaction(BaseModel):
- """A `SessionMessageCompaction` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- status: Any = None
-
- type: Literal["compaction"] | None = None
-
- id: SessionMessageID | None = None
-
- metadata: dict[str, Any] | None = None
-
- reason: OpenEnum[Literal["auto", "manual"]] | None = None
-
-
-class UnknownSessionMessageInfo(BaseModel):
- """A `SessionMessageInfo` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
- id: SessionMessageID | None = None
-
- metadata: dict[str, Any] | None = None
-
-
-class UnknownSessionStatsTools(BaseModel):
- """A `SessionStatsTools` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- mode: Any = None
-
-
-class UnknownSessionStatus(BaseModel):
- """A `SessionStatus` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
-
-class UnknownToolContent(BaseModel):
- """A `ToolContent` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
-
-class UnknownVoiceTransport(BaseModel):
- """A `VoiceTransport` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
-
-class UnknownVoiceTransportInput(BaseModel):
- """A `VoiceTransportInput` variant this client version does not recognize.
-
- Newer OpenCode servers can add variants. The raw fields are kept and available as attributes and through `model_extra`.
- """
-
- type: Any = None
-
-
-class VcsBase(BaseModel):
- name: str
-
- ref: str
-
- source: OpenEnum[Literal["reflog", "default"]]
-
-
-class VcsBaseResponse(BaseModel):
- location: LocationPublicRef
-
- data: VcsBase | None
-
-
-class VcsBranch(BaseModel):
- current: str | None = Field(default=None, exclude_if=is_none)
-
- default: str | None = Field(default=None, exclude_if=is_none)
-
-
-class VcsBranchListResponse(BaseModel):
- location: LocationPublicRef
-
- data: VcsBranchList
-
-
-class VcsBranchUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["vcs.branch.updated"] = "vcs.branch.updated"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: VcsBranchUpdatedEventData
-
-
-class VcsBranchUpdatedEventData(BaseModel):
- branch: str | None = Field(default=None, exclude_if=is_none)
-
-
-class VcsDiffResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[FileDiffInfo]
-
-
-class VcsFileStatus(BaseModel):
- file: str
-
- additions: int
-
- deletions: int
-
- status: OpenEnum[Literal["added", "deleted", "modified"]]
-
-
-class VcsGetResponse(BaseModel):
- location: LocationPublicRef
-
- data: VcsInfo
-
-
-class VcsInfo(BaseModel):
- provider: str | None = Field(default=None, exclude_if=is_none)
-
- branch: VcsBranch
-
-
-class VcsInitNotSupportedErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag",)
-
- tag: Literal["VcsInitNotSupportedError"] = Field(
- default="VcsInitNotSupportedError", validation_alias="_tag", serialization_alias="_tag"
- )
-
- provider_id: str = Field(validation_alias="providerID", serialization_alias="providerID")
-
- message: str
-
-
-class VcsStatusResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[VcsFileStatus]
-
-
-class VoiceSessionResponse(BaseModel):
- data: VoiceSessionResponseData
-
-
-class VoiceSessionResponseData(BaseModel):
- transport: VoiceTransport
-
-
-class VoiceWebRTCTransport(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["webrtc"] = "webrtc"
-
- sdp: str
-
-
-class VoiceWebRTCTransportInput(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["webrtc"] = "webrtc"
-
- sdp: str
-
- dcid: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]] | None = None
-
-
-class VoiceWebSocketTransport(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["websocket"] = "websocket"
-
- url: str
-
- token: str
-
- expires_at: float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]] = Field(
- validation_alias="expiresAt", serialization_alias="expiresAt"
- )
-
-
-class VoiceWebSocketTransportInput(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- type: Literal["websocket"] = "websocket"
-
-
-class WebSearchProvider(BaseModel):
- id: WebSearchID
-
- name: str
-
-
-class WebSearchResponse(BaseModel):
- provider_id: WebSearchID = Field(validation_alias="providerID", serialization_alias="providerID")
-
- results: list[WebSearchResult]
-
-
-class WebSearchResult(BaseModel):
- url: str
-
- title: str | None = Field(default=None, exclude_if=is_none)
-
- content: str | None = Field(default=None, exclude_if=is_none)
-
- time: WebSearchResultTime
-
-
-class WebSearchResultTime(BaseModel):
- published: float | None = Field(default=None, exclude_if=is_none)
-
-
-class WebsearchProvidersResponse(BaseModel):
- location: LocationPublicRef
-
- data: list[WebSearchProvider]
-
-
-class WebsearchQueryResponse(BaseModel):
- location: LocationPublicRef
-
- data: WebSearchResponse
-
-
-class WebsearchUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["websearch.updated"] = "websearch.updated"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: Any
-
-
-class WorktreeDirectory(BaseModel):
- directory: AbsolutePath
-
- strategy: str | None = Field(default=None, exclude_if=is_none)
-
-
-class WorktreeErrorBody(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("tag", "name")
-
- tag: Literal["WorktreeError"] = Field(default="WorktreeError", validation_alias="_tag", serialization_alias="_tag")
-
- name: Literal["WorktreeError"] = "WorktreeError"
-
- data: WorktreeErrorBodyData
-
-
-class WorktreeErrorBodyData(BaseModel):
- message: str
-
- force_required: bool | None = Field(
- default=None, validation_alias="forceRequired", serialization_alias="forceRequired"
- )
-
-
-class WorktreeInfo(BaseModel):
- directory: AbsolutePath
-
-
-class WorktreeResolvedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["worktree.resolved"] = "worktree.resolved"
-
- durable: WorktreeResolvedEventDurable
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: WorktreeResolvedEventData
-
-
-class WorktreeResolvedEventData(BaseModel):
- project_id: ProjectID = Field(validation_alias="projectID", serialization_alias="projectID")
-
- directory: AbsolutePath
-
- previous: ProjectID
-
- adopted: list[ProjectID] | None = Field(default=None, exclude_if=is_none)
-
-
-class WorktreeResolvedEventDurable(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("version",)
-
- aggregate_id: str = Field(validation_alias="aggregateID", serialization_alias="aggregateID")
-
- seq: EventSeq
-
- version: EventVersion = 1
-
-
-class WorktreeUpdatedEvent(BaseModel):
- __opencode_always_set__: ClassVar[tuple[str, ...]] = ("type",)
-
- id: EventID
-
- created: float
-
- metadata: dict[str, Any] | None = Field(default=None, exclude_if=is_none)
-
- type: Literal["worktree.updated"] = "worktree.updated"
-
- location: LocationRef | None = Field(default=None, exclude_if=is_none)
-
- data: WorktreeUpdatedEventData
-
-
-class WorktreeUpdatedEventData(BaseModel):
- project_id: ProjectID = Field(validation_alias="projectID", serialization_alias="projectID")
-
-
-AbsolutePath: TypeAlias = str
-
-
-AgentColor: TypeAlias = str
-
-
-AgentID: TypeAlias = str
-
-
-ConfigEntry: TypeAlias = Annotated[
- Annotated[ConfigDocument, Tag("document")]
- | Annotated[ConfigDirectory, Tag("directory")]
- | Annotated[UnknownConfigEntry, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["document", "directory"])),
-]
-
-
-ConfigInfoMcpServersValue: TypeAlias = Annotated[
- Annotated[McpLocalConfig, Tag("local")]
- | Annotated[McpRemoteConfig, Tag("remote")]
- | Annotated[UnknownConfigInfoMcpServersValue, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["local", "remote"])),
-]
-
-
-ConfigModelReasoning: TypeAlias = Annotated[
- Annotated[ConfigModelReasoningFalse, Tag("false")]
- | Annotated[ConfigModelReasoningTrue, Tag("true")]
- | Annotated[UnknownConfigModelReasoning, Tag("__unknown__")],
- Discriminator(discriminator("supported", "supported", ["false", "true"])),
-]
-
-
-ConnectionInfo: TypeAlias = Annotated[
- Annotated[ConnectionCredentialInfo, Tag("credential")]
- | Annotated[ConnectionEnvInfo, Tag("env")]
- | Annotated[UnknownConnectionInfo, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["credential", "env"])),
-]
-
-
-CredentialID: TypeAlias = str
-
-
-CredentialValue: TypeAlias = Annotated[
- Annotated[CredentialOAuth, Tag("oauth")]
- | Annotated[CredentialKey, Tag("key")]
- | Annotated[CredentialExternal, Tag("external")]
- | Annotated[UnknownCredentialValue, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["oauth", "key", "external"])),
-]
-
-
-Event: TypeAlias = Annotated[
- Annotated[LocationShutdownEvent, Tag("location.shutdown")]
- | Annotated[ModelsDevRefreshedEvent, Tag("models-dev.refreshed")]
- | Annotated[CredentialUpdatedEvent, Tag("credential.updated")]
- | Annotated[CredentialSwitchedEvent, Tag("credential.switched")]
- | Annotated[IntegrationUpdatedEvent, Tag("integration.updated")]
- | Annotated[ProviderUpdatedEvent, Tag("provider.updated")]
- | Annotated[ModelUpdatedEvent, Tag("model.updated")]
- | Annotated[AgentUpdatedEvent, Tag("agent.updated")]
- | Annotated[SessionCreatedEvent, Tag("session.created")]
- | Annotated[SessionAgentSelectedEvent, Tag("session.agent.selected")]
- | Annotated[SessionModelSelectedEvent, Tag("session.model.selected")]
- | Annotated[SessionMovedEvent, Tag("session.moved")]
- | Annotated[SessionRenamedEvent, Tag("session.renamed")]
- | Annotated[SessionMetadataUpdatedEvent, Tag("session.metadata.updated")]
- | Annotated[SessionPermissionsEvent, Tag("session.permissions")]
- | Annotated[SessionViewedEvent, Tag("session.viewed")]
- | Annotated[SessionArchivedEvent, Tag("session.archived")]
- | Annotated[SessionUnarchivedEvent, Tag("session.unarchived")]
- | Annotated[SessionUsageUpdatedEvent, Tag("session.usage.updated")]
- | Annotated[SessionDeletedEvent, Tag("session.deleted")]
- | Annotated[SessionForkedEvent, Tag("session.forked")]
- | Annotated[SessionInboxDeliveredEvent, Tag("session.inbox.delivered")]
- | Annotated[SessionInboxEnqueuedEvent, Tag("session.inbox.enqueued")]
- | Annotated[SessionInboxCancelledEvent, Tag("session.inbox.cancelled")]
- | Annotated[SessionInboxDeliveryChangedEvent, Tag("session.inbox.delivery.changed")]
- | Annotated[SessionExecutionStartedEvent, Tag("session.execution.started")]
- | Annotated[SessionExecutionSucceededEvent, Tag("session.execution.succeeded")]
- | Annotated[SessionExecutionFailedEvent, Tag("session.execution.failed")]
- | Annotated[SessionExecutionInterruptedEvent, Tag("session.execution.interrupted")]
- | Annotated[SessionInstructionsUpdatedEvent, Tag("session.instructions.updated")]
- | Annotated[SessionSyntheticEvent, Tag("session.synthetic")]
- | Annotated[SessionSkillActivatedEvent, Tag("session.skill.activated")]
- | Annotated[SessionShellStartedEvent, Tag("session.shell.started")]
- | Annotated[SessionShellEndedEvent, Tag("session.shell.ended")]
- | Annotated[SessionStepStartedEvent, Tag("session.step.started")]
- | Annotated[SessionStepStreamedEvent, Tag("session.step.streamed")]
- | Annotated[SessionStepEndedEvent, Tag("session.step.ended")]
- | Annotated[SessionStepFailedEvent, Tag("session.step.failed")]
- | Annotated[SessionTextStartedEvent, Tag("session.text.started")]
- | Annotated[SessionTextDeltaEvent, Tag("session.text.delta")]
- | Annotated[SessionTextEndedEvent, Tag("session.text.ended")]
- | Annotated[SessionReasoningStartedEvent, Tag("session.reasoning.started")]
- | Annotated[SessionReasoningDeltaEvent, Tag("session.reasoning.delta")]
- | Annotated[SessionReasoningEndedEvent, Tag("session.reasoning.ended")]
- | Annotated[SessionToolInputStartedEvent, Tag("session.tool.input.started")]
- | Annotated[SessionToolInputDeltaEvent, Tag("session.tool.input.delta")]
- | Annotated[SessionToolInputEndedEvent, Tag("session.tool.input.ended")]
- | Annotated[SessionToolCalledEvent, Tag("session.tool.called")]
- | Annotated[SessionToolProgressEvent, Tag("session.tool.progress")]
- | Annotated[SessionToolSuccessEvent, Tag("session.tool.success")]
- | Annotated[SessionToolFailedEvent, Tag("session.tool.failed")]
- | Annotated[SessionRetryScheduledEvent, Tag("session.retry.scheduled")]
- | Annotated[SessionCompactionStartedEvent, Tag("session.compaction.started")]
- | Annotated[SessionCompactionDeltaEvent, Tag("session.compaction.delta")]
- | Annotated[SessionCompactionEndedEvent, Tag("session.compaction.ended")]
- | Annotated[SessionCompactionFailedEvent, Tag("session.compaction.failed")]
- | Annotated[SessionRevertStagedEvent, Tag("session.revert.staged")]
- | Annotated[SessionRevertClearedEvent, Tag("session.revert.cleared")]
- | Annotated[SessionRevertCommittedEvent, Tag("session.revert.committed")]
- | Annotated[FilesystemChangedEvent, Tag("filesystem.changed")]
- | Annotated[ReferenceUpdatedEvent, Tag("reference.updated")]
- | Annotated[PermissionAskedEvent, Tag("permission.asked")]
- | Annotated[PermissionRepliedEvent, Tag("permission.replied")]
- | Annotated[PluginUpdatedEvent, Tag("plugin.updated")]
- | Annotated[ProjectUpdatedEvent, Tag("project.updated")]
- | Annotated[WorktreeUpdatedEvent, Tag("worktree.updated")]
- | Annotated[WorktreeResolvedEvent, Tag("worktree.resolved")]
- | Annotated[CommandUpdatedEvent, Tag("command.updated")]
- | Annotated[ConfigUpdatedEvent, Tag("config.updated")]
- | Annotated[SkillUpdatedEvent, Tag("skill.updated")]
- | Annotated[PtyCreatedEvent, Tag("pty.created")]
- | Annotated[PtyUpdatedEvent, Tag("pty.updated")]
- | Annotated[PtyExitedEvent, Tag("pty.exited")]
- | Annotated[PtyDeletedEvent, Tag("pty.deleted")]
- | Annotated[PersistentPtyAddedEvent, Tag("persistent-pty.added")]
- | Annotated[PersistentPtyRemovedEvent, Tag("persistent-pty.removed")]
- | Annotated[ShellCreatedEvent, Tag("shell.created")]
- | Annotated[ShellExitedEvent, Tag("shell.exited")]
- | Annotated[ShellDeletedEvent, Tag("shell.deleted")]
- | Annotated[FormCreatedEvent, Tag("form.created")]
- | Annotated[FormRepliedEvent, Tag("form.replied")]
- | Annotated[FormCancelledEvent, Tag("form.cancelled")]
- | Annotated[WebsearchUpdatedEvent, Tag("websearch.updated")]
- | Annotated[SessionStatusUpdated, Tag("session.status")]
- | Annotated[SessionIdleEvent, Tag("session.idle")]
- | Annotated[TuiPromptAppendEvent, Tag("tui.prompt.append")]
- | Annotated[TuiCommandExecuteEvent, Tag("tui.command.execute")]
- | Annotated[TuiToastShowEvent, Tag("tui.toast.show")]
- | Annotated[TuiSessionSelectEvent, Tag("tui.session.select")]
- | Annotated[InstallationUpdatedEvent, Tag("installation.updated")]
- | Annotated[InstallationUpdateAvailableEvent, Tag("installation.update-available")]
- | Annotated[VcsBranchUpdatedEvent, Tag("vcs.branch.updated")]
- | Annotated[McpStatusChangedEvent, Tag("mcp.status.changed")]
- | Annotated[McpResourcesChangedEvent, Tag("mcp.resources.changed")]
- | Annotated[RpcEvent, Tag("pattern:^rpc\\.[\\s\\S]*?$")]
- | Annotated[ServerConnectedEvent, Tag("server.connected")]
- | Annotated[UnknownEvent, Tag("__unknown__")],
- Discriminator(
- discriminator(
- "type",
- "type",
- [
- "location.shutdown",
- "models-dev.refreshed",
- "credential.updated",
- "credential.switched",
- "integration.updated",
- "provider.updated",
- "model.updated",
- "agent.updated",
- "session.created",
- "session.agent.selected",
- "session.model.selected",
- "session.moved",
- "session.renamed",
- "session.metadata.updated",
- "session.permissions",
- "session.viewed",
- "session.archived",
- "session.unarchived",
- "session.usage.updated",
- "session.deleted",
- "session.forked",
- "session.inbox.delivered",
- "session.inbox.enqueued",
- "session.inbox.cancelled",
- "session.inbox.delivery.changed",
- "session.execution.started",
- "session.execution.succeeded",
- "session.execution.failed",
- "session.execution.interrupted",
- "session.instructions.updated",
- "session.synthetic",
- "session.skill.activated",
- "session.shell.started",
- "session.shell.ended",
- "session.step.started",
- "session.step.streamed",
- "session.step.ended",
- "session.step.failed",
- "session.text.started",
- "session.text.delta",
- "session.text.ended",
- "session.reasoning.started",
- "session.reasoning.delta",
- "session.reasoning.ended",
- "session.tool.input.started",
- "session.tool.input.delta",
- "session.tool.input.ended",
- "session.tool.called",
- "session.tool.progress",
- "session.tool.success",
- "session.tool.failed",
- "session.retry.scheduled",
- "session.compaction.started",
- "session.compaction.delta",
- "session.compaction.ended",
- "session.compaction.failed",
- "session.revert.staged",
- "session.revert.cleared",
- "session.revert.committed",
- "filesystem.changed",
- "reference.updated",
- "permission.asked",
- "permission.replied",
- "plugin.updated",
- "project.updated",
- "worktree.updated",
- "worktree.resolved",
- "command.updated",
- "config.updated",
- "skill.updated",
- "pty.created",
- "pty.updated",
- "pty.exited",
- "pty.deleted",
- "persistent-pty.added",
- "persistent-pty.removed",
- "shell.created",
- "shell.exited",
- "shell.deleted",
- "form.created",
- "form.replied",
- "form.cancelled",
- "websearch.updated",
- "session.status",
- "session.idle",
- "tui.prompt.append",
- "tui.command.execute",
- "tui.toast.show",
- "tui.session.select",
- "installation.updated",
- "installation.update-available",
- "vcs.branch.updated",
- "mcp.status.changed",
- "mcp.resources.changed",
- "server.connected",
- ],
- patterns=[("^rpc\\.[\\s\\S]*?$", "pattern:^rpc\\.[\\s\\S]*?$")],
- )
- ),
-]
-
-
-EventID: TypeAlias = str
-
-
-EventSeq: TypeAlias = int
-
-
-EventVersion: TypeAlias = Literal[1]
-
-
-EventVersion10: TypeAlias = Literal[2]
-
-
-FormValue: TypeAlias = str | float | OpenEnum[Literal["Infinity", "-Infinity", "NaN"]] | bool | list[str]
-
-
-FormAnswer: TypeAlias = dict[str, FormValue]
-
-
-FormField: TypeAlias = Annotated[
- Annotated[FormStringField, Tag("string")]
- | Annotated[FormNumberField, Tag("number")]
- | Annotated[FormIntegerField, Tag("integer")]
- | Annotated[FormBooleanField, Tag("boolean")]
- | Annotated[FormMultiselectField, Tag("multiselect")]
- | Annotated[FormExternalField, Tag("external")]
- | Annotated[UnknownFormField, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["string", "number", "integer", "boolean", "multiselect", "external"])),
-]
-
-
-FormFields: TypeAlias = list[FormField]
-
-
-FormID: TypeAlias = str
-
-
-FormMetadata: TypeAlias = dict[str, Any]
-
-
-FormState: TypeAlias = Annotated[
- Annotated[FormStatePending, Tag("pending")]
- | Annotated[FormStateAnswered, Tag("answered")]
- | Annotated[FormStateCancelled, Tag("cancelled")]
- | Annotated[UnknownFormState, Tag("__unknown__")],
- Discriminator(discriminator("status", "status", ["pending", "answered", "cancelled"])),
-]
-
-
-InstructionEntryKey: TypeAlias = str
-"""Instruction entry key (lowercase alphanumerics plus . _ -)"""
-
-
-InstructionEntrySnapshot: TypeAlias = list[InstructionEntrySnapshotItem]
-
-
-InstructionHash: TypeAlias = str
-
-
-InstructionKey: TypeAlias = str
-
-
-IntegrationAttemptID: TypeAlias = str
-
-
-IntegrationAttemptStatus: TypeAlias = Annotated[
- Annotated[IntegrationAttemptStatusPending, Tag("pending")]
- | Annotated[IntegrationAttemptStatusComplete, Tag("complete")]
- | Annotated[IntegrationAttemptStatusFailed, Tag("failed")]
- | Annotated[IntegrationAttemptStatusExpired, Tag("expired")]
- | Annotated[UnknownIntegrationAttemptStatus, Tag("__unknown__")],
- Discriminator(discriminator("status", "status", ["pending", "complete", "failed", "expired"])),
-]
-
-
-IntegrationCommandAttemptStatus: TypeAlias = Annotated[
- Annotated[IntegrationCommandAttemptStatusPending, Tag("pending")]
- | Annotated[IntegrationCommandAttemptStatusComplete, Tag("complete")]
- | Annotated[IntegrationCommandAttemptStatusFailed, Tag("failed")]
- | Annotated[IntegrationCommandAttemptStatusExpired, Tag("expired")]
- | Annotated[UnknownIntegrationCommandAttemptStatus, Tag("__unknown__")],
- Discriminator(discriminator("status", "status", ["pending", "complete", "failed", "expired"])),
-]
-
-
-IntegrationID: TypeAlias = str
-
-
-IntegrationMethod: TypeAlias = Annotated[
- Annotated[IntegrationOAuthMethod, Tag("oauth")]
- | Annotated[IntegrationCommandMethod, Tag("command")]
- | Annotated[IntegrationKeyMethod, Tag("key")]
- | Annotated[IntegrationExternalMethod, Tag("external")]
- | Annotated[IntegrationEnvMethod, Tag("env")]
- | Annotated[UnknownIntegrationMethod, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["oauth", "command", "key", "external", "env"])),
-]
-
-
-IntegrationMethodID: TypeAlias = str
-
-
-McpAddConfig: TypeAlias = Annotated[
- Annotated[McpLocalConfig, Tag("local")]
- | Annotated[McpRemoteConfig, Tag("remote")]
- | Annotated[UnknownMcpAddConfig, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["local", "remote"])),
-]
-
-
-McpProtocol: TypeAlias = OpenEnum[Literal["legacy", "auto", "2026-07-28"]]
-"""MCP protocol negotiation. "legacy" (default) opens with the initialize handshake and speaks protocol revisions up to 2025-11-25. "auto" probes for the 2026-07-28 revision and falls back to legacy when the server does not support it. "2026-07-28" requires that revision and fails otherwise."""
-
-
-McpServerStatus: TypeAlias = Annotated[
- Annotated[McpStatusConnected, Tag("connected")]
- | Annotated[McpStatusPending, Tag("pending")]
- | Annotated[McpStatusDisabled, Tag("disabled")]
- | Annotated[McpStatusFailed, Tag("failed")]
- | Annotated[McpStatusNeedsAuth, Tag("needs_auth")]
- | Annotated[UnknownMcpServerStatus, Tag("__unknown__")],
- Discriminator(discriminator("status", "status", ["connected", "pending", "disabled", "failed", "needs_auth"])),
-]
-
-
-ModelCacheFormat: TypeAlias = OpenEnum[Literal["anthropic", "alibaba"]]
-
-
-ModelMaxTokensField: TypeAlias = OpenEnum[Literal["max_completion_tokens", "max_tokens"]]
-
-
-ModelReasoningField: TypeAlias = OpenEnum[Literal["reasoning", "reasoning_content", "reasoning_text"]] | str
-
-
-MoneyUSD: TypeAlias = float
-
-
-MoneyUSDPerMillionTokens: TypeAlias = float
-
-
-PermissionEffect: TypeAlias = OpenEnum[Literal["allow", "deny", "ask"]]
-
-
-PermissionID: TypeAlias = str
-
-
-PermissionReply: TypeAlias = OpenEnum[Literal["once", "always", "reject"]]
-
-
-PermissionRuleset: TypeAlias = list[PermissionRule]
-
-
-PermissionSavedID: TypeAlias = str
-
-
-PersistentPtyReadLines: TypeAlias = int
-
-
-PluginSource: TypeAlias = Annotated[
- Annotated[PluginSourceBuiltin, Tag("builtin")]
- | Annotated[PluginSourcePackage, Tag("package")]
- | Annotated[PluginSourceLocal, Tag("local")]
- | Annotated[PluginSourceSdk, Tag("sdk")]
- | Annotated[UnknownPluginSource, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["builtin", "package", "local", "sdk"])),
-]
-
-
-PluginState: TypeAlias = Annotated[
- Annotated[PluginStateActive, Tag("active")]
- | Annotated[PluginStateFailed, Tag("failed")]
- | Annotated[UnknownPluginState, Tag("__unknown__")],
- Discriminator(discriminator("status", "status", ["active", "failed"])),
-]
-
-
-ProjectID: TypeAlias = str
-
-
-ProjectVcs: TypeAlias = str
-
-
-PromptBase64: TypeAlias = str
-
-
-PromptFileSource: TypeAlias = Annotated[
- Annotated[PromptFileSourceInline, Tag("inline")]
- | Annotated[PromptFileSourceUri, Tag("uri")]
- | Annotated[UnknownPromptFileSource, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["inline", "uri"])),
-]
-
-
-ProviderCompaction: TypeAlias = Annotated[
- Annotated[ProviderCompactionSummary, Tag("summary")]
- | Annotated[ProviderCompactionNative, Tag("native")]
- | Annotated[UnknownProviderCompaction, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["summary", "native"])),
-]
-
-
-ProviderEncoding: TypeAlias = OpenEnum[Literal["zstd", "gzip", "none"]]
-
-
-ProviderID: TypeAlias = str
-
-
-ProviderTransport: TypeAlias = OpenEnum[Literal["http", "websocket"]]
-
-
-PtyID: TypeAlias = str
-
-
-ReferenceSource: TypeAlias = Annotated[
- Annotated[ReferenceLocalSource, Tag("local")]
- | Annotated[ReferenceGitSource, Tag("git")]
- | Annotated[UnknownReferenceSource, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["local", "git"])),
-]
-
-
-RelativePath: TypeAlias = str
-
-
-SessionEventDurable: TypeAlias = Annotated[
- Annotated[SessionCreatedEvent, Tag("session.created")]
- | Annotated[SessionAgentSelectedEvent, Tag("session.agent.selected")]
- | Annotated[SessionModelSelectedEvent, Tag("session.model.selected")]
- | Annotated[SessionMovedEvent, Tag("session.moved")]
- | Annotated[SessionRenamedEvent, Tag("session.renamed")]
- | Annotated[SessionMetadataUpdatedEvent, Tag("session.metadata.updated")]
- | Annotated[SessionPermissionsEvent, Tag("session.permissions")]
- | Annotated[SessionViewedEvent, Tag("session.viewed")]
- | Annotated[SessionArchivedEvent, Tag("session.archived")]
- | Annotated[SessionUnarchivedEvent, Tag("session.unarchived")]
- | Annotated[SessionDeletedEvent, Tag("session.deleted")]
- | Annotated[SessionForkedEvent, Tag("session.forked")]
- | Annotated[SessionInboxDeliveredEvent, Tag("session.inbox.delivered")]
- | Annotated[SessionInboxEnqueuedEvent, Tag("session.inbox.enqueued")]
- | Annotated[SessionInboxCancelledEvent, Tag("session.inbox.cancelled")]
- | Annotated[SessionInboxDeliveryChangedEvent, Tag("session.inbox.delivery.changed")]
- | Annotated[SessionExecutionStartedEvent, Tag("session.execution.started")]
- | Annotated[SessionExecutionSucceededEvent, Tag("session.execution.succeeded")]
- | Annotated[SessionExecutionFailedEvent, Tag("session.execution.failed")]
- | Annotated[SessionExecutionInterruptedEvent, Tag("session.execution.interrupted")]
- | Annotated[SessionInstructionsUpdatedEvent, Tag("session.instructions.updated")]
- | Annotated[SessionSyntheticEvent, Tag("session.synthetic")]
- | Annotated[SessionSkillActivatedEvent, Tag("session.skill.activated")]
- | Annotated[SessionShellStartedEvent, Tag("session.shell.started")]
- | Annotated[SessionShellEndedEvent, Tag("session.shell.ended")]
- | Annotated[SessionStepStartedEvent, Tag("session.step.started")]
- | Annotated[SessionStepStreamedEvent, Tag("session.step.streamed")]
- | Annotated[SessionStepEndedEvent, Tag("session.step.ended")]
- | Annotated[SessionStepFailedEvent, Tag("session.step.failed")]
- | Annotated[SessionTextStartedEvent, Tag("session.text.started")]
- | Annotated[SessionTextEndedEvent, Tag("session.text.ended")]
- | Annotated[SessionReasoningStartedEvent, Tag("session.reasoning.started")]
- | Annotated[SessionReasoningEndedEvent, Tag("session.reasoning.ended")]
- | Annotated[SessionToolInputStartedEvent, Tag("session.tool.input.started")]
- | Annotated[SessionToolInputEndedEvent, Tag("session.tool.input.ended")]
- | Annotated[SessionToolCalledEvent, Tag("session.tool.called")]
- | Annotated[SessionToolSuccessEvent, Tag("session.tool.success")]
- | Annotated[SessionToolFailedEvent, Tag("session.tool.failed")]
- | Annotated[SessionRetryScheduledEvent, Tag("session.retry.scheduled")]
- | Annotated[SessionCompactionStartedEvent, Tag("session.compaction.started")]
- | Annotated[SessionCompactionEndedEvent, Tag("session.compaction.ended")]
- | Annotated[SessionCompactionFailedEvent, Tag("session.compaction.failed")]
- | Annotated[SessionRevertStagedEvent, Tag("session.revert.staged")]
- | Annotated[SessionRevertClearedEvent, Tag("session.revert.cleared")]
- | Annotated[SessionRevertCommittedEvent, Tag("session.revert.committed")]
- | Annotated[SessionUsageRecordedEvent, Tag("session.usage.recorded")]
- | Annotated[SessionMessageContentUpdatedEvent, Tag("session.message.content.updated")]
- | Annotated[UnknownSessionEventDurable, Tag("__unknown__")],
- Discriminator(
- discriminator(
- "type",
- "type",
- [
- "session.created",
- "session.agent.selected",
- "session.model.selected",
- "session.moved",
- "session.renamed",
- "session.metadata.updated",
- "session.permissions",
- "session.viewed",
- "session.archived",
- "session.unarchived",
- "session.deleted",
- "session.forked",
- "session.inbox.delivered",
- "session.inbox.enqueued",
- "session.inbox.cancelled",
- "session.inbox.delivery.changed",
- "session.execution.started",
- "session.execution.succeeded",
- "session.execution.failed",
- "session.execution.interrupted",
- "session.instructions.updated",
- "session.synthetic",
- "session.skill.activated",
- "session.shell.started",
- "session.shell.ended",
- "session.step.started",
- "session.step.streamed",
- "session.step.ended",
- "session.step.failed",
- "session.text.started",
- "session.text.ended",
- "session.reasoning.started",
- "session.reasoning.ended",
- "session.tool.input.started",
- "session.tool.input.ended",
- "session.tool.called",
- "session.tool.success",
- "session.tool.failed",
- "session.retry.scheduled",
- "session.compaction.started",
- "session.compaction.ended",
- "session.compaction.failed",
- "session.revert.staged",
- "session.revert.cleared",
- "session.revert.committed",
- "session.usage.recorded",
- "session.message.content.updated",
- ],
- )
- ),
-]
-
-
-SessionForkBoundary: TypeAlias = Annotated[
- Annotated[SessionForkBoundaryBefore, Tag("before")]
- | Annotated[SessionForkBoundaryThrough, Tag("through")]
- | Annotated[UnknownSessionForkBoundary, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["before", "through"])),
-]
-
-
-SessionID: TypeAlias = str
-
-
-SessionInboxCompactionPayload: TypeAlias = Any
-
-
-SessionInboxDelivery: TypeAlias = OpenEnum[Literal["steer", "queue"]]
-
-
-SessionInboxInfo: TypeAlias = Annotated[
- Annotated[SessionInboxUser, Tag("user")]
- | Annotated[SessionInboxSynthetic, Tag("synthetic")]
- | Annotated[SessionInboxCompaction, Tag("compaction")]
- | Annotated[SessionInboxMove, Tag("move")]
- | Annotated[UnknownSessionInboxInfo, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["user", "synthetic", "compaction", "move"])),
-]
-
-
-SessionInboxItem: TypeAlias = Annotated[
- Annotated[SessionInboxItemUser, Tag("user")]
- | Annotated[SessionInboxItemSynthetic, Tag("synthetic")]
- | Annotated[SessionInboxItemCompaction, Tag("compaction")]
- | Annotated[SessionInboxItemMove, Tag("move")]
- | Annotated[UnknownSessionInboxItem, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["user", "synthetic", "compaction", "move"])),
-]
-
-
-SessionLogItem: TypeAlias = SessionEventDurable | EventLogSynced
-
-
-SessionMessageAssistantContent: TypeAlias = Annotated[
- Annotated[SessionMessageAssistantText, Tag("text")]
- | Annotated[SessionMessageAssistantReasoning, Tag("reasoning")]
- | Annotated[SessionMessageAssistantTool, Tag("tool")]
- | Annotated[UnknownSessionMessageAssistantContent, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["text", "reasoning", "tool"])),
-]
-
-
-SessionMessageAssistantContentItem: TypeAlias = Annotated[
- Annotated[SessionMessageAssistantText, Tag("text")]
- | Annotated[SessionMessageAssistantReasoning, Tag("reasoning")]
- | Annotated[SessionMessageAssistantTool, Tag("tool")]
- | Annotated[UnknownSessionMessageAssistantContentItem, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["text", "reasoning", "tool"])),
-]
-
-
-SessionMessageAssistantToolState: TypeAlias = Annotated[
- Annotated[SessionMessageToolStateStreaming, Tag("streaming")]
- | Annotated[SessionMessageToolStateRunning, Tag("running")]
- | Annotated[SessionMessageToolStateCompleted, Tag("completed")]
- | Annotated[SessionMessageToolStateError, Tag("error")]
- | Annotated[UnknownSessionMessageAssistantToolState, Tag("__unknown__")],
- Discriminator(discriminator("status", "status", ["streaming", "running", "completed", "error"])),
-]
-
-
-SessionMessageCompaction: TypeAlias = Annotated[
- Annotated[SessionMessageCompactionRunning, Tag("running")]
- | Annotated[SessionMessageCompactionCompleted, Tag("completed")]
- | Annotated[SessionMessageCompactionFailed, Tag("failed")]
- | Annotated[UnknownSessionMessageCompaction, Tag("__unknown__")],
- Discriminator(discriminator("status", "status", ["running", "completed", "failed"])),
-]
-
-
-SessionMessageID: TypeAlias = str
-
-
-SessionMessageInfo: TypeAlias = Annotated[
- Annotated[SessionMessageAgentSelected, Tag("agent-switched")]
- | Annotated[SessionMessageModelSelected, Tag("model-switched")]
- | Annotated[SessionMessageLocationSwitched, Tag("location-switched")]
- | Annotated[SessionMessageUser, Tag("user")]
- | Annotated[SessionMessageSynthetic, Tag("synthetic")]
- | Annotated[SessionMessageSystem, Tag("system")]
- | Annotated[SessionMessageSkill, Tag("skill")]
- | Annotated[SessionMessageShell, Tag("shell")]
- | Annotated[SessionMessageAssistant, Tag("assistant")]
- | Annotated[SessionMessageCompaction, Tag("compaction")]
- | Annotated[SessionMessageIdle, Tag("idle")]
- | Annotated[UnknownSessionMessageInfo, Tag("__unknown__")],
- Discriminator(
- discriminator(
- "type",
- "type",
- [
- "agent-switched",
- "model-switched",
- "location-switched",
- "user",
- "synthetic",
- "system",
- "skill",
- "shell",
- "assistant",
- "compaction",
- "idle",
- ],
- )
- ),
-]
-
-
-SessionMessageNative: TypeAlias = dict[str, Any]
-
-
-SessionMetadata: TypeAlias = dict[str, Any]
-
-
-SessionStatsTools: TypeAlias = Annotated[
- Annotated[SessionStatsToolsNone, Tag("none")]
- | Annotated[SessionStatsToolsSummary, Tag("summary")]
- | Annotated[SessionStatsToolsDetail, Tag("detail")]
- | Annotated[UnknownSessionStatsTools, Tag("__unknown__")],
- Discriminator(discriminator("mode", "mode", ["none", "summary", "detail"])),
-]
-
-
-SessionStatus: TypeAlias = Annotated[
- Annotated[SessionStatusIdle, Tag("idle")]
- | Annotated[SessionStatusRetry, Tag("retry")]
- | Annotated[SessionStatusBusy, Tag("busy")]
- | Annotated[UnknownSessionStatus, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["idle", "retry", "busy"])),
-]
-
-
-SessionsCursor: TypeAlias = str
-"""Opaque pagination cursor returned as cursor.previous or cursor.next in the previous response."""
-
-
-ShellID: TypeAlias = str
-
-
-SkillID: TypeAlias = str
-
-
-SkillName: TypeAlias = str
-
-
-SnapshotID: TypeAlias = str
-
-
-ToolContent: TypeAlias = Annotated[
- Annotated[ToolTextContent, Tag("text")]
- | Annotated[ToolFileContent, Tag("file")]
- | Annotated[UnknownToolContent, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["text", "file"])),
-]
-
-
-VcsBranchList: TypeAlias = list[str]
-
-
-VcsMode: TypeAlias = OpenEnum[Literal["working", "branch", "committed"]]
-
-
-VoiceTransport: TypeAlias = Annotated[
- Annotated[VoiceWebRTCTransport, Tag("webrtc")]
- | Annotated[VoiceWebSocketTransport, Tag("websocket")]
- | Annotated[UnknownVoiceTransport, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["webrtc", "websocket"])),
-]
-
-
-VoiceTransportInput: TypeAlias = Annotated[
- Annotated[VoiceWebRTCTransportInput, Tag("webrtc")]
- | Annotated[VoiceWebSocketTransportInput, Tag("websocket")]
- | Annotated[UnknownVoiceTransportInput, Tag("__unknown__")],
- Discriminator(discriminator("type", "type", ["webrtc", "websocket"])),
-]
-
-
-WebSearchID: TypeAlias = str
-
-
-WorkspaceID: TypeAlias = str
-
-
-WorktreeList: TypeAlias = list[WorktreeDirectory]
-
-
-__all__ = [
- "AbsolutePath",
- "AgentColor",
- "AgentGetResponse",
- "AgentID",
- "AgentInfo",
- "AgentListResponse",
- "AgentNotFoundErrorBody",
- "AgentUpdatedEvent",
- "CommandExecutionErrorBody",
- "CommandInfo",
- "CommandListResponse",
- "CommandNotFoundErrorBody",
- "CommandUpdatedEvent",
- "ConfigAgent",
- "ConfigAgentModel",
- "ConfigAgentRequest",
- "ConfigCommand",
- "ConfigCommandModel",
- "ConfigDirectory",
- "ConfigDocument",
- "ConfigEntry",
- "ConfigFormatterEntry",
- "ConfigInfo",
- "ConfigInfoCompaction",
- "ConfigInfoCompactionKeep",
- "ConfigInfoEnterprise",
- "ConfigInfoExperimental",
- "ConfigInfoExperimentalPoliciesItem",
- "ConfigInfoLspOption2Value",
- "ConfigInfoMcp",
- "ConfigInfoMcpServersValue",
- "ConfigInfoMcpTimeout",
- "ConfigInfoMedia",
- "ConfigInfoMediaImage",
- "ConfigInfoModel",
- "ConfigInfoPoliciesItem",
- "ConfigInfoToolOutput",
- "ConfigInfoWatcher",
- "ConfigLSPServer",
- "ConfigModel",
- "ConfigModelCapabilities",
- "ConfigModelCost",
- "ConfigModelCostCache",
- "ConfigModelCostTier",
- "ConfigModelLimit",
- "ConfigModelReasoning",
- "ConfigModelReasoningFalse",
- "ConfigModelReasoningTrue",
- "ConfigModelReasoningTrueBudget",
- "ConfigModelSettings",
- "ConfigModelVariantsItem",
- "ConfigPluginEntry",
- "ConfigProvider",
- "ConfigProviderSettings",
- "ConfigReferenceGit",
- "ConfigReferenceLocal",
- "ConfigShellOption",
- "ConfigUpdatedEvent",
- "ConfigWarming",
- "ConfigWebSearchInfo",
- "ConfigWorktree",
- "ConflictErrorBody",
- "ConnectionCredentialInfo",
- "ConnectionEnvInfo",
- "ConnectionInfo",
- "ConnectionStatus",
- "CredentialEntry",
- "CredentialExternal",
- "CredentialID",
- "CredentialKey",
- "CredentialOAuth",
- "CredentialSwitchedEvent",
- "CredentialSwitchedEventData",
- "CredentialUpdatedEvent",
- "CredentialValue",
- "Event",
- "EventID",
- "EventLogSynced",
- "EventSeq",
- "EventVersion",
- "EventVersion10",
- "ExperimentalPersistentPtyCreateSize",
- "ExperimentalPersistentPtyHandoffResponse",
- "ExperimentalPersistentPtyUpdateSize",
- "FileDiffInfo",
- "FileFindResponse",
- "FileListResponse",
- "FileNotFoundErrorBody",
- "FileSystemEntry",
- "FileSystemWrite",
- "FileWriteResponse",
- "FilesystemChangedEvent",
- "FilesystemChangedEventData",
- "ForbiddenErrorBody",
- "FormAlreadySettledErrorBody",
- "FormAnswer",
- "FormBooleanField",
- "FormCancelledEvent",
- "FormCancelledEventData",
- "FormCreatedEvent",
- "FormCreatedEventData",
- "FormDetail",
- "FormExternalField",
- "FormField",
- "FormFields",
- "FormID",
- "FormInfo",
- "FormIntegerField",
- "FormInvalidAnswerErrorBody",
- "FormListResponse",
- "FormMetadata",
- "FormMultiselectField",
- "FormNotFoundErrorBody",
- "FormNumberField",
- "FormOption",
- "FormRepliedEvent",
- "FormRepliedEventData",
- "FormState",
- "FormStateAnswered",
- "FormStateCancelled",
- "FormStatePending",
- "FormStringField",
- "FormValue",
- "FormWhen",
- "GenerateTextResponse",
- "GenerateTextResponseData",
- "InstallationUpdateAvailableEvent",
- "InstallationUpdateAvailableEventData",
- "InstallationUpdatedEvent",
- "InstallationUpdatedEventData",
- "InstructionEntryInfo",
- "InstructionEntryKey",
- "InstructionEntrySnapshot",
- "InstructionEntrySnapshotItem",
- "InstructionEntryValueTooLargeErrorBody",
- "InstructionHash",
- "InstructionKey",
- "IntegrationAttempt",
- "IntegrationAttemptID",
- "IntegrationAttemptNotFoundErrorBody",
- "IntegrationAttemptStatus",
- "IntegrationAttemptStatusComplete",
- "IntegrationAttemptStatusCompleteTime",
- "IntegrationAttemptStatusExpired",
- "IntegrationAttemptStatusExpiredTime",
- "IntegrationAttemptStatusFailed",
- "IntegrationAttemptStatusFailedTime",
- "IntegrationAttemptStatusPending",
- "IntegrationAttemptStatusPendingTime",
- "IntegrationAttemptTime",
- "IntegrationCommandAttempt",
- "IntegrationCommandAttemptStatus",
- "IntegrationCommandAttemptStatusComplete",
- "IntegrationCommandAttemptStatusCompleteTime",
- "IntegrationCommandAttemptStatusExpired",
- "IntegrationCommandAttemptStatusExpiredTime",
- "IntegrationCommandAttemptStatusFailed",
- "IntegrationCommandAttemptStatusFailedTime",
- "IntegrationCommandAttemptStatusPending",
- "IntegrationCommandAttemptStatusPendingTime",
- "IntegrationCommandAttemptTime",
- "IntegrationCommandConnectResponse",
- "IntegrationCommandMethod",
- "IntegrationCommandStatusResponse",
- "IntegrationEnvMethod",
- "IntegrationExternalMethod",
- "IntegrationGetResponse",
- "IntegrationID",
- "IntegrationInfo",
- "IntegrationKeyMethod",
- "IntegrationListResponse",
- "IntegrationMethod",
- "IntegrationMethodID",
- "IntegrationMethodNotFoundErrorBody",
- "IntegrationNotFoundErrorBody",
- "IntegrationOAuthMethod",
- "IntegrationOauthConnectResponse",
- "IntegrationOauthStatusResponse",
- "IntegrationUpdatedEvent",
- "InvalidCursorErrorBody",
- "InvalidRequestErrorBody",
- "Location",
- "LocationNotFoundErrorBody",
- "LocationPublicInfo",
- "LocationPublicInfoProject",
- "LocationPublicRef",
- "LocationRef",
- "LocationShutdownEvent",
- "McpAddConfig",
- "McpListResponse",
- "McpLocalConfig",
- "McpLocalConfigTimeout",
- "McpOAuthConfig",
- "McpProtocol",
- "McpRemoteConfig",
- "McpRemoteConfigTimeout",
- "McpResource",
- "McpResourceCatalog",
- "McpResourceCatalogResponse",
- "McpResourceTemplate",
- "McpResourcesChangedEvent",
- "McpResourcesChangedEventData",
- "McpServer",
- "McpServerNotFoundErrorBody",
- "McpServerStatus",
- "McpStatusChangedEvent",
- "McpStatusChangedEventData",
- "McpStatusConnected",
- "McpStatusDisabled",
- "McpStatusFailed",
- "McpStatusNeedsAuth",
- "McpStatusPending",
- "MessageNotFoundErrorBody",
- "MigrationV1StatusResponseOption1",
- "MigrationV1StatusResponseOption2",
- "MigrationV1StatusResponseOption2Progress",
- "MigrationV1StatusResponseOption3",
- "ModelCacheFormat",
- "ModelCapabilities",
- "ModelCompatibility",
- "ModelCost",
- "ModelCostCache",
- "ModelCostTier",
- "ModelDefaultResponse",
- "ModelInfo",
- "ModelInfoLimit",
- "ModelInfoTime",
- "ModelListResponse",
- "ModelMaxTokensField",
- "ModelReasoningField",
- "ModelRef",
- "ModelSettings",
- "ModelUpdatedEvent",
- "ModelVariant",
- "ModelsDevRefreshedEvent",
- "MoneyUSD",
- "MoneyUSDPerMillionTokens",
- "PairingCode",
- "PairingSession",
- "PermissionAskedEvent",
- "PermissionAskedEventData",
- "PermissionCreateResponse",
- "PermissionEffect",
- "PermissionID",
- "PermissionNotFoundErrorBody",
- "PermissionRepliedEvent",
- "PermissionRepliedEventData",
- "PermissionReply",
- "PermissionRequest",
- "PermissionRequestListResponse",
- "PermissionRule",
- "PermissionRuleset",
- "PermissionSavedID",
- "PermissionSavedInfo",
- "PermissionSavedInfoTime",
- "PermissionSavedNotFoundErrorBody",
- "PermissionSource",
- "PersistentPtyAddedEvent",
- "PersistentPtyAddedEventData",
- "PersistentPtyHandoff",
- "PersistentPtyInfo",
- "PersistentPtyInfoOutput",
- "PersistentPtyInfoSize",
- "PersistentPtyReadLines",
- "PersistentPtyReadResult",
- "PersistentPtyReadResultScreen",
- "PersistentPtyReadResultScreenCursor",
- "PersistentPtyRemovedEvent",
- "PersistentPtyRemovedEventData",
- "PersistentPtySnapshot",
- "PersistentPtySnapshotCursor",
- "PluginCheckResponse",
- "PluginFeatures",
- "PluginInfo",
- "PluginListResponse",
- "PluginSource",
- "PluginSourceBuiltin",
- "PluginSourceLocal",
- "PluginSourcePackage",
- "PluginSourceSdk",
- "PluginState",
- "PluginStateActive",
- "PluginStateFailed",
- "PluginUpdatedEvent",
- "Project",
- "ProjectCommands",
- "ProjectID",
- "ProjectIcon",
- "ProjectNotFoundErrorBody",
- "ProjectTime",
- "ProjectUpdatedEvent",
- "ProjectUpdatedEventData",
- "ProjectVcs",
- "PromptAgentAttachment",
- "PromptBase64",
- "PromptFileAttachment",
- "PromptFileSource",
- "PromptFileSourceInline",
- "PromptFileSourceUri",
- "PromptInputFileAttachment",
- "PromptInputSkillAttachment",
- "PromptMention",
- "PromptSkillAttachment",
- "ProviderCompaction",
- "ProviderCompactionNative",
- "ProviderCompactionSummary",
- "ProviderCompatibility",
- "ProviderEncoding",
- "ProviderGetResponse",
- "ProviderID",
- "ProviderInfo",
- "ProviderListResponse",
- "ProviderNotFoundErrorBody",
- "ProviderRequest",
- "ProviderSettings",
- "ProviderTransport",
- "ProviderUpdatedEvent",
- "Pty",
- "PtyConnectTokenResponse",
- "PtyCreateResponse",
- "PtyCreatedEvent",
- "PtyCreatedEventData",
- "PtyDeletedEvent",
- "PtyDeletedEventData",
- "PtyExitedEvent",
- "PtyExitedEventData",
- "PtyGetResponse",
- "PtyID",
- "PtyListResponse",
- "PtyNotFoundErrorBody",
- "PtyTicketConnectToken",
- "PtyUpdateResponse",
- "PtyUpdateSize",
- "PtyUpdatedEvent",
- "PtyUpdatedEventData",
- "ReferenceGitSource",
- "ReferenceInfo",
- "ReferenceListResponse",
- "ReferenceLocalSource",
- "ReferenceSource",
- "ReferenceUpdatedEvent",
- "RelativePath",
- "RpcErrorBody",
- "RpcEvent",
- "RpcInternalErrorBody",
- "RpcOutput",
- "ServerConnectedEvent",
- "ServerInfo",
- "ServerInfoCapabilities",
- "ServerInfoPaths",
- "ServiceUnavailableErrorBody",
- "SessionActive",
- "SessionAgentSelectedEvent",
- "SessionAgentSelectedEventData",
- "SessionAgentSelectedEventDurable",
- "SessionArchivedEvent",
- "SessionArchivedEventData",
- "SessionArchivedEventDurable",
- "SessionBusyErrorBody",
- "SessionCompactionDeltaEvent",
- "SessionCompactionDeltaEventData",
- "SessionCompactionEndedEvent",
- "SessionCompactionEndedEventData",
- "SessionCompactionEndedEventDurable",
- "SessionCompactionFailedEvent",
- "SessionCompactionFailedEventData",
- "SessionCompactionFailedEventDurable",
- "SessionCompactionStartedEvent",
- "SessionCompactionStartedEventData",
- "SessionCompactionStartedEventDurable",
- "SessionCreatedEvent",
- "SessionCreatedEventData",
- "SessionCreatedEventDurable",
- "SessionDeletedEvent",
- "SessionDeletedEventData",
- "SessionDeletedEventDurable",
- "SessionEventDurable",
- "SessionExecutionFailedEvent",
- "SessionExecutionFailedEventData",
- "SessionExecutionFailedEventDurable",
- "SessionExecutionInterruptedEvent",
- "SessionExecutionInterruptedEventData",
- "SessionExecutionInterruptedEventDurable",
- "SessionExecutionStartedEvent",
- "SessionExecutionStartedEventData",
- "SessionExecutionStartedEventDurable",
- "SessionExecutionSucceededEvent",
- "SessionExecutionSucceededEventData",
- "SessionExecutionSucceededEventDurable",
- "SessionForkBoundary",
- "SessionForkBoundaryBefore",
- "SessionForkBoundaryThrough",
- "SessionForkedEvent",
- "SessionForkedEventData",
- "SessionForkedEventDurable",
- "SessionGenerateResponse",
- "SessionGenerateResponseData",
- "SessionID",
- "SessionIdleEvent",
- "SessionIdleEventData",
- "SessionInboxCancelledEvent",
- "SessionInboxCancelledEventData",
- "SessionInboxCancelledEventDurable",
- "SessionInboxCompaction",
- "SessionInboxCompactionPayload",
- "SessionInboxCompactionTime",
- "SessionInboxDeliveredEvent",
- "SessionInboxDeliveredEventData",
- "SessionInboxDeliveredEventDurable",
- "SessionInboxDelivery",
- "SessionInboxDeliveryChangedEvent",
- "SessionInboxDeliveryChangedEventData",
- "SessionInboxDeliveryChangedEventDurable",
- "SessionInboxEnqueuedEvent",
- "SessionInboxEnqueuedEventData",
- "SessionInboxEnqueuedEventDurable",
- "SessionInboxInfo",
- "SessionInboxItem",
- "SessionInboxItemCompaction",
- "SessionInboxItemMove",
- "SessionInboxItemSynthetic",
- "SessionInboxItemUser",
- "SessionInboxMove",
- "SessionInboxMovePayload",
- "SessionInboxMovePayload1",
- "SessionInboxMoveTime",
- "SessionInboxSynthetic",
- "SessionInboxSyntheticPayload",
- "SessionInboxSyntheticTime",
- "SessionInboxUser",
- "SessionInboxUserPayload",
- "SessionInboxUserTime",
- "SessionInfo",
- "SessionInfoFork",
- "SessionInfoTime",
- "SessionInstructionsUpdatedEvent",
- "SessionInstructionsUpdatedEventData",
- "SessionInstructionsUpdatedEventDurable",
- "SessionInterruptResponse",
- "SessionLogItem",
- "SessionMessageAgentSelected",
- "SessionMessageAgentSelectedTime",
- "SessionMessageAssistant",
- "SessionMessageAssistantContent",
- "SessionMessageAssistantContentItem",
- "SessionMessageAssistantReasoning",
- "SessionMessageAssistantReasoningTime",
- "SessionMessageAssistantRetry",
- "SessionMessageAssistantSnapshot",
- "SessionMessageAssistantText",
- "SessionMessageAssistantTime",
- "SessionMessageAssistantTool",
- "SessionMessageAssistantToolState",
- "SessionMessageAssistantToolTime",
- "SessionMessageCompaction",
- "SessionMessageCompactionCompleted",
- "SessionMessageCompactionCompletedTime",
- "SessionMessageCompactionFailed",
- "SessionMessageCompactionFailedTime",
- "SessionMessageCompactionRunning",
- "SessionMessageCompactionRunningTime",
- "SessionMessageContentUpdatedEvent",
- "SessionMessageContentUpdatedEventData",
- "SessionMessageContentUpdatedEventDurable",
- "SessionMessageID",
- "SessionMessageIdle",
- "SessionMessageIdleTime",
- "SessionMessageInfo",
- "SessionMessageLocationSwitched",
- "SessionMessageLocationSwitchedPrevious",
- "SessionMessageLocationSwitchedTime",
- "SessionMessageModelSelected",
- "SessionMessageModelSelectedTime",
- "SessionMessageNative",
- "SessionMessageShell",
- "SessionMessageShellOutput",
- "SessionMessageShellTime",
- "SessionMessageSkill",
- "SessionMessageSkillTime",
- "SessionMessageSynthetic",
- "SessionMessageSyntheticTime",
- "SessionMessageSystem",
- "SessionMessageSystemTime",
- "SessionMessageToolStateCompleted",
- "SessionMessageToolStateError",
- "SessionMessageToolStateRunning",
- "SessionMessageToolStateStreaming",
- "SessionMessageUser",
- "SessionMessageUserTime",
- "SessionMessagesResponse",
- "SessionMessagesResponseCursor",
- "SessionMetadata",
- "SessionMetadataUpdatedEvent",
- "SessionMetadataUpdatedEventData",
- "SessionMetadataUpdatedEventDurable",
- "SessionModelSelectedEvent",
- "SessionModelSelectedEventData",
- "SessionModelSelectedEventDurable",
- "SessionMovedEvent",
- "SessionMovedEventData",
- "SessionMovedEventDurable",
- "SessionNotFoundErrorBody",
- "SessionPermissionsEvent",
- "SessionPermissionsEventData",
- "SessionPermissionsEventDurable",
- "SessionReasoningDeltaEvent",
- "SessionReasoningDeltaEventData",
- "SessionReasoningEndedEvent",
- "SessionReasoningEndedEventData",
- "SessionReasoningEndedEventDurable",
- "SessionReasoningStartedEvent",
- "SessionReasoningStartedEventData",
- "SessionReasoningStartedEventDurable",
- "SessionRenamedEvent",
- "SessionRenamedEventData",
- "SessionRenamedEventDurable",
- "SessionRetryScheduledEvent",
- "SessionRetryScheduledEventData",
- "SessionRetryScheduledEventDurable",
- "SessionRevert",
- "SessionRevertClearedEvent",
- "SessionRevertClearedEventData",
- "SessionRevertClearedEventDurable",
- "SessionRevertCommittedEvent",
- "SessionRevertCommittedEventData",
- "SessionRevertCommittedEventDurable",
- "SessionRevertStagedEvent",
- "SessionRevertStagedEventData",
- "SessionRevertStagedEventDurable",
- "SessionShellEndedEvent",
- "SessionShellEndedEventData",
- "SessionShellEndedEventDataOutput",
- "SessionShellEndedEventDurable",
- "SessionShellStartedEvent",
- "SessionShellStartedEventData",
- "SessionShellStartedEventDurable",
- "SessionSkillActivatedEvent",
- "SessionSkillActivatedEventData",
- "SessionSkillActivatedEventDurable",
- "SessionStatsActivity",
- "SessionStatsInfo",
- "SessionStatsInfoRange",
- "SessionStatsModelUsage",
- "SessionStatsToolTotals",
- "SessionStatsToolUsage",
- "SessionStatsTools",
- "SessionStatsToolsDetail",
- "SessionStatsToolsNone",
- "SessionStatsToolsSummary",
- "SessionStatus",
- "SessionStatusBusy",
- "SessionStatusIdle",
- "SessionStatusRetry",
- "SessionStatusRetryAction",
- "SessionStatusUpdated",
- "SessionStatusUpdatedData",
- "SessionStepEndedEvent",
- "SessionStepEndedEventData",
- "SessionStepEndedEventDurable",
- "SessionStepFailedEvent",
- "SessionStepFailedEventData",
- "SessionStepFailedEventDurable",
- "SessionStepStartedEvent",
- "SessionStepStartedEventData",
- "SessionStepStartedEventDurable",
- "SessionStepStreamedEvent",
- "SessionStepStreamedEventData",
- "SessionStepStreamedEventDurable",
- "SessionStructuredError",
- "SessionStructuredErrorResponse",
- "SessionSyntheticEvent",
- "SessionSyntheticEventData",
- "SessionSyntheticEventDurable",
- "SessionTextDeltaEvent",
- "SessionTextDeltaEventData",
- "SessionTextEndedEvent",
- "SessionTextEndedEventData",
- "SessionTextEndedEventDurable",
- "SessionTextStartedEvent",
- "SessionTextStartedEventData",
- "SessionTextStartedEventDurable",
- "SessionToolCalledEvent",
- "SessionToolCalledEventData",
- "SessionToolCalledEventDurable",
- "SessionToolFailedEvent",
- "SessionToolFailedEventData",
- "SessionToolFailedEventDurable",
- "SessionToolInputDeltaEvent",
- "SessionToolInputDeltaEventData",
- "SessionToolInputEndedEvent",
- "SessionToolInputEndedEventData",
- "SessionToolInputEndedEventDurable",
- "SessionToolInputStartedEvent",
- "SessionToolInputStartedEventData",
- "SessionToolInputStartedEventDurable",
- "SessionToolProgressEvent",
- "SessionToolProgressEventData",
- "SessionToolSuccessEvent",
- "SessionToolSuccessEventData",
- "SessionToolSuccessEventDurable",
- "SessionTransferData",
- "SessionUnarchivedEvent",
- "SessionUnarchivedEventData",
- "SessionUnarchivedEventDurable",
- "SessionUsageRecordedEvent",
- "SessionUsageRecordedEventData",
- "SessionUsageRecordedEventDurable",
- "SessionUsageUpdatedEvent",
- "SessionUsageUpdatedEventData",
- "SessionViewedEvent",
- "SessionViewedEventData",
- "SessionViewedEventDurable",
- "SessionsCursor",
- "SessionsResponse",
- "SessionsResponseCursor",
- "ShellCreateResponse",
- "ShellCreatedEvent",
- "ShellCreatedEventData",
- "ShellDeletedEvent",
- "ShellDeletedEventData",
- "ShellExitedEvent",
- "ShellExitedEventData",
- "ShellGetResponse",
- "ShellID",
- "ShellInfo",
- "ShellInfoTime",
- "ShellListResponse",
- "ShellNotFoundErrorBody",
- "ShellOutputResponse",
- "ShellOutputResponseData",
- "SkillID",
- "SkillInfo",
- "SkillListResponse",
- "SkillName",
- "SkillNotFoundErrorBody",
- "SkillUpdatedEvent",
- "SnapshotID",
- "TokenUsageInfo",
- "TokenUsageInfoCache",
- "ToolContent",
- "ToolFileContent",
- "ToolTextContent",
- "TuiCommandExecuteEvent",
- "TuiCommandExecuteEventData",
- "TuiPromptAppendEvent",
- "TuiPromptAppendEventData",
- "TuiSessionSelectEvent",
- "TuiSessionSelectEventData",
- "TuiToastShowEvent",
- "TuiToastShowEventData",
- "UnauthorizedErrorBody",
- "UnknownConfigEntry",
- "UnknownConfigInfoMcpServersValue",
- "UnknownConfigModelReasoning",
- "UnknownConnectionInfo",
- "UnknownCredentialValue",
- "UnknownErrorBody",
- "UnknownEvent",
- "UnknownFormField",
- "UnknownFormState",
- "UnknownIntegrationAttemptStatus",
- "UnknownIntegrationCommandAttemptStatus",
- "UnknownIntegrationMethod",
- "UnknownMcpAddConfig",
- "UnknownMcpServerStatus",
- "UnknownPluginSource",
- "UnknownPluginState",
- "UnknownPromptFileSource",
- "UnknownProviderCompaction",
- "UnknownReferenceSource",
- "UnknownSessionEventDurable",
- "UnknownSessionForkBoundary",
- "UnknownSessionInboxInfo",
- "UnknownSessionInboxItem",
- "UnknownSessionMessageAssistantContent",
- "UnknownSessionMessageAssistantContentItem",
- "UnknownSessionMessageAssistantToolState",
- "UnknownSessionMessageCompaction",
- "UnknownSessionMessageInfo",
- "UnknownSessionStatsTools",
- "UnknownSessionStatus",
- "UnknownToolContent",
- "UnknownVoiceTransport",
- "UnknownVoiceTransportInput",
- "VcsBase",
- "VcsBaseResponse",
- "VcsBranch",
- "VcsBranchList",
- "VcsBranchListResponse",
- "VcsBranchUpdatedEvent",
- "VcsBranchUpdatedEventData",
- "VcsDiffResponse",
- "VcsFileStatus",
- "VcsGetResponse",
- "VcsInfo",
- "VcsInitNotSupportedErrorBody",
- "VcsMode",
- "VcsStatusResponse",
- "VoiceSessionResponse",
- "VoiceSessionResponseData",
- "VoiceTransport",
- "VoiceTransportInput",
- "VoiceWebRTCTransport",
- "VoiceWebRTCTransportInput",
- "VoiceWebSocketTransport",
- "VoiceWebSocketTransportInput",
- "WebSearchID",
- "WebSearchProvider",
- "WebSearchResponse",
- "WebSearchResult",
- "WebSearchResultTime",
- "WebsearchProvidersResponse",
- "WebsearchQueryResponse",
- "WebsearchUpdatedEvent",
- "WorkspaceID",
- "WorktreeDirectory",
- "WorktreeErrorBody",
- "WorktreeErrorBodyData",
- "WorktreeInfo",
- "WorktreeList",
- "WorktreeResolvedEvent",
- "WorktreeResolvedEventData",
- "WorktreeResolvedEventDurable",
- "WorktreeUpdatedEvent",
- "WorktreeUpdatedEventData",
-]
From c6ab338547c22c0c49f643df9035cd7d1d339194 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 17:46:05 +0530
Subject: [PATCH 21/41] chore: regenerate SDK
Ferrule-Generated-From: b258175b899062c2c803ea7d5ede91399b42f6c7
---
ferrule.lock | 13 +++++--------
src/opencode/resources/debug/debug.py | 3 ---
src/opencode/resources/experimental/experimental.py | 3 ---
src/opencode/resources/migration/migration.py | 3 ---
.../resources/session/instructions/instructions.py | 3 ---
5 files changed, 5 insertions(+), 20 deletions(-)
diff --git a/ferrule.lock b/ferrule.lock
index 7ab2580..dd81b45 100644
--- a/ferrule.lock
+++ b/ferrule.lock
@@ -1,10 +1,7 @@
{
- "config_sha256": "13d15125c26cc8e7c7ccc11f57dcfc79a8455f50d969516b379b9b813222077c",
+ "config_sha256": "15dfaee2c804c26b4cbef2629f59339943f72747b495f40afd375fb61c51c59c",
"ferrule_version": "0.1.0",
"files": {
- ".github/workflows/ci.yml": "5ac1be1c103477e7dcdb28d276ff14fb4ebf92650e92d6912e605a64df2dd27b",
- ".github/workflows/publish-pypi.yml": "9d8ec64e69efcd63590be59b6aaa38669da06b8d1e473872fa82e6b572ea1626",
- ".github/workflows/release-doctor.yml": "f1dcc10ac7b6f434cf8a6bcdfc3d069c6ff00f65f09e1274986816f38f05993d",
"AGENTS.md": "7f135189e8ab203aea9abecf018f905e395767cd38c24c2d22c3c1230c04c5e5",
"api.md": "ebffd584aab5b25583dafdf80606b3283d711c49c844666df908d7e19d62f4fa",
"examples/agent/agent_list.py": "8f50db0f50024f2b1b315f8f6c08f825445172fbef62b6350751498a2e1db9a9",
@@ -92,11 +89,11 @@
"src/opencode/resources/config.py": "df61a96eca71052b06735ccd78a9d9c30bdf9663a25fd42b6e3cd21637177390",
"src/opencode/resources/credential.py": "4e9620f0d4d4016a9fc21373348e14376adf504db5cdfecefa8d041fb1bf7313",
"src/opencode/resources/debug/__init__.py": "4b259cbe61b2fece4ebf71e4287a286f8440020daf88219543a9c7dd3038ec82",
- "src/opencode/resources/debug/debug.py": "3257e35af342049cbb2377d97a78136bb35793eb89b1cacfed4e019bf024c2bd",
+ "src/opencode/resources/debug/debug.py": "287c2d81c3b29a0166d3e9ad1c4b0e22054c385518dce277ea72e41ea22e14ec",
"src/opencode/resources/debug/location.py": "314e488618b6b3aa5d8528edad62c5bc98a1b085c3fe94a8077847ce4e8a25b1",
"src/opencode/resources/event.py": "1726d900bda94f6a9dda4929787e96972b8d5e11492b9294c5be25e2c11af854",
"src/opencode/resources/experimental/__init__.py": "28feac464d3d9902214d3960dede255c9564614645757eae8faa7df956ab4877",
- "src/opencode/resources/experimental/experimental.py": "30e7249ccce9f09e96fe456a89e6f8e603769bb7438f195a984c3824e20960c5",
+ "src/opencode/resources/experimental/experimental.py": "23b0215a9099b6cbce704b960d93ad0ebfd1f5becef97d94bcac5f6cf4cc9a70",
"src/opencode/resources/experimental/persistent_pty.py": "524bf688f9117c384e789317b11f382af48801470de5e44a97197336177435e2",
"src/opencode/resources/file.py": "d33d2de768514bec732c430628ba04715be7917ee417abe8da38fd7e22f239af",
"src/opencode/resources/form.py": "f10e411104e93a9ac2c57fd9e3b8c93610a8ccd873e98e5c87345f48c0534664",
@@ -113,7 +110,7 @@
"src/opencode/resources/mcp/resource.py": "77be8c8239069effdbdb1e1cdb394cf9d6fb73e0b432390665bd11e9ffed49ef",
"src/opencode/resources/message.py": "6d7c16aa68a3ee3f3357198035ddd699538c92b3432eeaafcd043a668d8bc2b3",
"src/opencode/resources/migration/__init__.py": "4de52a3e69dc12cfa1f3c325d0e451b251117d689b1d2067adf8701d6262a82d",
- "src/opencode/resources/migration/migration.py": "2351d44babd9ce815676e40d74acb870cea50fa86664766c561729e899264f56",
+ "src/opencode/resources/migration/migration.py": "20834214c4183d26be738c421bfc2e520ddeb01d05abb304c24c95f843ee5564",
"src/opencode/resources/migration/v1.py": "054977ec18a7974e22f870f203652286406e3af93e4131dcd5b04ce8af764026",
"src/opencode/resources/model.py": "2ed756685b0a5967075474e428f82cdea42bb37067e8eb0985ad3afe970be705",
"src/opencode/resources/permission/__init__.py": "fb877b53463f24574016eb09ec406c43bcce5207e0ceb25c2ee6be02f755486b",
@@ -134,7 +131,7 @@
"src/opencode/resources/session/inbox.py": "0690130066ea767e0eee2c048d1dfb1e6f576672b1b80099072c42bacbea5555",
"src/opencode/resources/session/instructions/__init__.py": "0f5a6aead7a12d2a4f73046d763f3e65444201724cc069e6793e97a22e34c22d",
"src/opencode/resources/session/instructions/entry.py": "8989b4410230a2ad7bc50979f1c96b3487a4f674138b386849f9d28f3209d023",
- "src/opencode/resources/session/instructions/instructions.py": "af2fca01a9c6ee019b23871a86c48aeb65a8ab10d8fa50ecaa87247fef4918bc",
+ "src/opencode/resources/session/instructions/instructions.py": "615106051353e999b83b97f3017a454be2fe303d34372bbc2c4bb6b96479b834",
"src/opencode/resources/session/message.py": "7fa8727aca864415ba8a8b69d09157f2b76f81cf43b59081e4a597388d415ec0",
"src/opencode/resources/session/revert.py": "4c82af7a765f5081c18152940fc3ad0ed26f43f0b93ed2ea4e513027599ed0ad",
"src/opencode/resources/session/session.py": "f3ff63d37ad95bb2e903339e4f9618aba7e6fade3721cd47fa7ff34212241576",
diff --git a/src/opencode/resources/debug/debug.py b/src/opencode/resources/debug/debug.py
index 5e4a56d..fa5da0b 100644
--- a/src/opencode/resources/debug/debug.py
+++ b/src/opencode/resources/debug/debug.py
@@ -2,9 +2,6 @@
from __future__ import annotations
-import httpx
-
-from ..._types import Body, Query, Headers, NotGiven, not_given
from .location import (
LocationResource,
AsyncLocationResource,
diff --git a/src/opencode/resources/experimental/experimental.py b/src/opencode/resources/experimental/experimental.py
index 0f58419..d8dfbf3 100644
--- a/src/opencode/resources/experimental/experimental.py
+++ b/src/opencode/resources/experimental/experimental.py
@@ -2,9 +2,6 @@
from __future__ import annotations
-import httpx
-
-from ..._types import Body, Query, Headers, NotGiven, not_given
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from .persistent_pty import (
diff --git a/src/opencode/resources/migration/migration.py b/src/opencode/resources/migration/migration.py
index 06b9074..000a69e 100644
--- a/src/opencode/resources/migration/migration.py
+++ b/src/opencode/resources/migration/migration.py
@@ -2,8 +2,6 @@
from __future__ import annotations
-import httpx
-
from .v1 import (
V1Resource,
AsyncV1Resource,
@@ -12,7 +10,6 @@
V1ResourceWithStreamingResponse,
AsyncV1ResourceWithStreamingResponse,
)
-from ..._types import Body, Query, Headers, NotGiven, not_given
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
diff --git a/src/opencode/resources/session/instructions/instructions.py b/src/opencode/resources/session/instructions/instructions.py
index a29bb45..a9f58b1 100644
--- a/src/opencode/resources/session/instructions/instructions.py
+++ b/src/opencode/resources/session/instructions/instructions.py
@@ -2,8 +2,6 @@
from __future__ import annotations
-import httpx
-
from .entry import (
EntryResource,
AsyncEntryResource,
@@ -12,7 +10,6 @@
EntryResourceWithStreamingResponse,
AsyncEntryResourceWithStreamingResponse,
)
-from ...._types import Body, Query, Headers, NotGiven, not_given
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
From ef8d9e5a6c85f04072a526695cb257f3f6106b9d Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 17:56:20 +0530
Subject: [PATCH 22/41] chore: regenerate SDK
Ferrule-Generated-From: 5b493c49ab1b98bccff990ca796bf126472cecf6
---
examples/agent/session_form_create.py | 4 +-
ferrule.lock | 20 ++--
skills/opencode/SKILL.md | 4 +-
src/opencode/_agent/SKILL.md | 4 +-
src/opencode/resources/message.py | 6 +-
src/opencode/resources/migration/v1.py | 28 +++--
tests/api_resources/session/test_form.py | 40 +++----
tests/api_resources/test_credential.py | 16 +--
tests/api_resources/test_generate.py | 4 +-
tests/api_resources/test_permission.py | 4 +-
tests/api_resources/test_session.py | 132 +++++++++++------------
11 files changed, 135 insertions(+), 127 deletions(-)
diff --git a/examples/agent/session_form_create.py b/examples/agent/session_form_create.py
index cbe439a..65a9689 100644
--- a/examples/agent/session_form_create.py
+++ b/examples/agent/session_form_create.py
@@ -14,8 +14,8 @@
"format": "email",
"hidden": True,
"key": "string",
- "maxLength": 0,
- "minLength": 0,
+ "max_length": 0,
+ "min_length": 0,
"options": [
{
"description": "string",
diff --git a/ferrule.lock b/ferrule.lock
index dd81b45..1d05305 100644
--- a/ferrule.lock
+++ b/ferrule.lock
@@ -35,7 +35,7 @@
"examples/agent/rpc_call.py": "c89e2ad5e860682c6da65adbd5b76ce1087fab4abf598fff4f26e03288c50bc4",
"examples/agent/server_info.py": "0070e224f86aab73b5812ab1574a0d95cbae0a24a6805de26993cf33dd621241",
"examples/agent/session_create.py": "f1796fc1c024f01457eda899063930ead30d288a52e678a813675a6d4b604ed6",
- "examples/agent/session_form_create.py": "b76b8bb5c0bbf862e63364dcaf96793efdebd5267ea690e5eb9e85ef3167dcb1",
+ "examples/agent/session_form_create.py": "261d985bce87939c94eb240b43a18b487e039d811d8a2903c1e00ffe321593c2",
"examples/agent/session_inbox_update.py": "0586f72cbe3ec53325c87d941c839546fb576b45551709adff1d799e6cb4baaf",
"examples/agent/session_instructions_entry_list.py": "850a49ed898caf7c3c358ba58e47716af0af46088e5e4876106cab95082b9476",
"examples/agent/session_message_get.py": "4aa849478ce5cf99c67d138e6586991bf0b5b5ff03c11bc51e78725e570e97c0",
@@ -49,10 +49,10 @@
"examples/agent/worktree_create.py": "c5222de9df346efdafc2e7b7c12449aa502a29e309731c54a9581406e0eaaf59",
"llms.txt": "c9d8fa2e8580039de36b140ca06f2cb5558ff3d2b42e16effbd3a46ed682eef4",
"scripts/utils/ruffen-docs.py": "da4c57fe200f40d65dc9cefdfb1fa65245c407daa595b6522f43c44f14b271af",
- "skills/opencode/SKILL.md": "6db853ae6cfec6c88e9207591ed71545d14e9665bdafc993e0994c9b8898c3ca",
+ "skills/opencode/SKILL.md": "2f5b8093c857e2b1078025f941ef2a934915305116b388537091efeb57791788",
"src/opencode/__init__.py": "9706cd16d41b78c7d20f1f32d04196bb7c11ffb5372dae6cf750eef7118db961",
"src/opencode/_agent/AGENTS.md": "7f135189e8ab203aea9abecf018f905e395767cd38c24c2d22c3c1230c04c5e5",
- "src/opencode/_agent/SKILL.md": "6db853ae6cfec6c88e9207591ed71545d14e9665bdafc993e0994c9b8898c3ca",
+ "src/opencode/_agent/SKILL.md": "2f5b8093c857e2b1078025f941ef2a934915305116b388537091efeb57791788",
"src/opencode/_agent/llms.txt": "c9d8fa2e8580039de36b140ca06f2cb5558ff3d2b42e16effbd3a46ed682eef4",
"src/opencode/_base_client.py": "32888441549574c13267ece8bfbc34394bfb41e6bef39633207fa8381805aae7",
"src/opencode/_client.py": "d14617ca1aea9d2266756cceba3e5c16c6b9b2b35fcf489a26128549cbec7684",
@@ -108,10 +108,10 @@
"src/opencode/resources/mcp/__init__.py": "6909d57eb83ae7e40888c5d59b5a1b16385dcb31eb41745bb5ed2a431d5c0959",
"src/opencode/resources/mcp/mcp.py": "ca0a3ce12c799155bf9f8d8468722336a50710241c238746558f8c5ba388f1ba",
"src/opencode/resources/mcp/resource.py": "77be8c8239069effdbdb1e1cdb394cf9d6fb73e0b432390665bd11e9ffed49ef",
- "src/opencode/resources/message.py": "6d7c16aa68a3ee3f3357198035ddd699538c92b3432eeaafcd043a668d8bc2b3",
+ "src/opencode/resources/message.py": "0afa589aa5a0ed7dafa25ee8812329f2e6f4230791167d5b57f9c926a076f771",
"src/opencode/resources/migration/__init__.py": "4de52a3e69dc12cfa1f3c325d0e451b251117d689b1d2067adf8701d6262a82d",
"src/opencode/resources/migration/migration.py": "20834214c4183d26be738c421bfc2e520ddeb01d05abb304c24c95f843ee5564",
- "src/opencode/resources/migration/v1.py": "054977ec18a7974e22f870f203652286406e3af93e4131dcd5b04ce8af764026",
+ "src/opencode/resources/migration/v1.py": "015ae91b65a32c19232102e470d5852a54ab62c96bdc6010ff3181256012ab68",
"src/opencode/resources/model.py": "2ed756685b0a5967075474e428f82cdea42bb37067e8eb0985ad3afe970be705",
"src/opencode/resources/permission/__init__.py": "fb877b53463f24574016eb09ec406c43bcce5207e0ceb25c2ee6be02f755486b",
"src/opencode/resources/permission/permission.py": "fa0dd4c37e21766810e4a43f23257c1c928fe86704359d18894d4bca6b9a24bb",
@@ -775,24 +775,24 @@
"tests/api_resources/session/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/session/instructions/__init__.py": "9e36a6e4132a941a78fd9e044311e0bf22e3dad2d760c839db2757c1730c20c2",
"tests/api_resources/session/instructions/test_entry.py": "46b598f854ae1c7a63239751e12e8d23d65e5012f4a053129d8976a9b053a23b",
- "tests/api_resources/session/test_form.py": "49f4212fc26fccb69ab6a7d160961cfb99284a95d154c61a7b3618c4d890d189",
+ "tests/api_resources/session/test_form.py": "abd9a4c13e4fd91bbe437cdadd9438593f83d0022565da3f5f45b1905ddc44c0",
"tests/api_resources/session/test_inbox.py": "c89953a98bca37a57cb76a668a5ef23ed8cf6fdb94388146ceb9316e6e448b45",
"tests/api_resources/session/test_message.py": "9f02aa1f294d39aac79ed24daa2b65e3b78666830b281014ddaa76f4e33c138f",
"tests/api_resources/session/test_revert.py": "e1e1df2eb510bfa0d9d57299d61eef9bd4f2c5eaa4e2e99937fa22ada21f78a9",
"tests/api_resources/test_agent.py": "7f6c7a832a84884620e1da85e4b2bb8529ee711ff31cec23563f4fa9d1d51a2a",
"tests/api_resources/test_command.py": "8be3e96e7ff2ad6750fc1085ce6d422ca8e1cda3b2443dca2de0953c8f9edee5",
"tests/api_resources/test_config.py": "3b646f5206a3143c00ad183ba3a1f7cbc0a588058e8ab9751d316609cd5da218",
- "tests/api_resources/test_credential.py": "3158903c567e31b5938f1efdab5c8a214284eafe1a46059289c3216e8c74eb26",
+ "tests/api_resources/test_credential.py": "3a96a5ad26af91e8aabd96e39a3ece187a33e46f72d4d2e8c6a4eeda470eda42",
"tests/api_resources/test_event.py": "49bfe90f1ace1b9808391d06f4b5f04528873414d05df5f1fdede2543af6d0e0",
"tests/api_resources/test_file.py": "6b62a1a08c9a76d179e5829e530986df09586373a747986667edda47f4977e33",
"tests/api_resources/test_form.py": "35fdd2d079242efe0a6c26c06198910ed89a2cc1421e5f74509dd202fd830ae5",
- "tests/api_resources/test_generate.py": "f4156c880368a6bcfce1b68220835fdfe17e9704f874a783be46397b71b69bf6",
+ "tests/api_resources/test_generate.py": "ac4d7310b6b55ad12fde320efe39d9109a8e8cf2ade306e2c6b5fc104ae4fe47",
"tests/api_resources/test_integration.py": "e2ae3ea7fcff53120d0ee3c990f6bc7517e281cd03909e3f74c33cf535269bcc",
"tests/api_resources/test_location.py": "40f6986acf63ed7e0e279c9df41e970405ac2474f95928f28112921d8d16f13d",
"tests/api_resources/test_mcp.py": "aa097634cc812da7210cbbfd3364faf2e4abf7ed6df03a45240a50e3e194b077",
"tests/api_resources/test_message.py": "60e274837f502bad62c52cf1300639b7d137279cfb0dae49a2268abde4cac6b7",
"tests/api_resources/test_model.py": "afd0aa11174a17f25cb7a060597d913202359074c20676ee8b0dabed853548bd",
- "tests/api_resources/test_permission.py": "ff26711b38971fdc8833432996b19d821a9f1f9de4b973f239e7eb35c44da2af",
+ "tests/api_resources/test_permission.py": "fc5e3aba8d35d478ae7fc6cd15b3187a4d8babc530ca2341a9ea154580a39fd7",
"tests/api_resources/test_plugin.py": "2685fa322627066f274e068959165468524db459aa47684976f57e13f35bb05b",
"tests/api_resources/test_project.py": "726fcff5e8fd957199484659e32fac34a90195bb731a1710d1c9c1cec6d3d6ed",
"tests/api_resources/test_provider.py": "e83de14c66e0265613e5c0afd79cae2935a501699ee03d2aa184c21c9117b6b5",
@@ -800,7 +800,7 @@
"tests/api_resources/test_reference.py": "82661f6cecc6f0fe44d63fd1180817b27869845578f5d71d039916ee1679b8ec",
"tests/api_resources/test_rpc.py": "1dabbd8dbdab2902c05721ea5c344bd57f4c4e5c85af15c9ff60dd650baf4907",
"tests/api_resources/test_server.py": "bf49f7693308b7ede9fd1cf2e4e555dce88ec560c6e908393020e3ebc7fac9db",
- "tests/api_resources/test_session.py": "86d5ae01518ac3fc53750d397c0d113c6c8f28b39271491684241a6c5ca40d03",
+ "tests/api_resources/test_session.py": "fa1a643e5985dd7677daff01fe094ff2dda0a43b46bce8cd7856287ab0c5f0c0",
"tests/api_resources/test_shell.py": "544c8e14854989a874fc1e6e5ca9067ccae4bfd44f81ea8a845fa101176181d4",
"tests/api_resources/test_skill.py": "cf22c66273943ec809067cfac8d6b9d14f71e93f862b1be615ae2c51933c34cf",
"tests/api_resources/test_vcs.py": "502249bc039765b74d0860cc151f48f1cc5f2ad4456d123b2c950253411e6bb1",
diff --git a/skills/opencode/SKILL.md b/skills/opencode/SKILL.md
index ea64563..26e8249 100644
--- a/skills/opencode/SKILL.md
+++ b/skills/opencode/SKILL.md
@@ -458,8 +458,8 @@ form = client.session.form.create(
"format": "email",
"hidden": True,
"key": "string",
- "maxLength": 0,
- "minLength": 0,
+ "max_length": 0,
+ "min_length": 0,
"options": [
{
"description": "string",
diff --git a/src/opencode/_agent/SKILL.md b/src/opencode/_agent/SKILL.md
index ea64563..26e8249 100644
--- a/src/opencode/_agent/SKILL.md
+++ b/src/opencode/_agent/SKILL.md
@@ -458,8 +458,8 @@ form = client.session.form.create(
"format": "email",
"hidden": True,
"key": "string",
- "maxLength": 0,
- "minLength": 0,
+ "max_length": 0,
+ "min_length": 0,
"options": [
{
"description": "string",
diff --git a/src/opencode/resources/message.py b/src/opencode/resources/message.py
index fe91b6d..7a32666 100644
--- a/src/opencode/resources/message.py
+++ b/src/opencode/resources/message.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Optional
+from typing import Any, Optional, cast
from typing_extensions import Literal
import httpx
@@ -119,7 +119,7 @@ def list(
message_list_params.MessageListParams,
),
),
- model=SessionMessageInfo,
+ model=cast(Any, SessionMessageInfo), # Union types cannot be passed in as arguments in the type system
)
@@ -216,7 +216,7 @@ def list(
message_list_params.MessageListParams,
),
),
- model=SessionMessageInfo,
+ model=cast(Any, SessionMessageInfo), # Union types cannot be passed in as arguments in the type system
)
diff --git a/src/opencode/resources/migration/v1.py b/src/opencode/resources/migration/v1.py
index 37f22df..910c7b9 100644
--- a/src/opencode/resources/migration/v1.py
+++ b/src/opencode/resources/migration/v1.py
@@ -2,6 +2,8 @@
from __future__ import annotations
+from typing import Any, cast
+
import httpx
from ..._types import Body, Query, Headers, NotGiven, not_given
@@ -50,12 +52,15 @@ def status(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> V1StatusResponse:
"""Return the progress of the V1 to V2 session history migration."""
- return self._get(
- "/api/experimental/migration/v1",
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ return cast(
+ V1StatusResponse,
+ self._get(
+ "/api/experimental/migration/v1",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=cast(Any, V1StatusResponse), # Union types cannot be passed in as arguments in the type system
),
- cast_to=V1StatusResponse,
)
@@ -90,12 +95,15 @@ async def status(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> V1StatusResponse:
"""Return the progress of the V1 to V2 session history migration."""
- return await self._get(
- "/api/experimental/migration/v1",
- options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ return cast(
+ V1StatusResponse,
+ await self._get(
+ "/api/experimental/migration/v1",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=cast(Any, V1StatusResponse), # Union types cannot be passed in as arguments in the type system
),
- cast_to=V1StatusResponse,
)
diff --git a/tests/api_resources/session/test_form.py b/tests/api_resources/session/test_form.py
index d1b8c56..0672ee1 100644
--- a/tests/api_resources/session/test_form.py
+++ b/tests/api_resources/session/test_form.py
@@ -30,8 +30,8 @@ def test_method_create(self, client: Opencode) -> None:
"format": "email",
"hidden": True,
"key": "string",
- "maxLength": 0,
- "minLength": 0,
+ "max_length": 0,
+ "min_length": 0,
"options": [
{
"description": "string",
@@ -69,8 +69,8 @@ def test_method_create_with_all_params(self, client: Opencode) -> None:
"format": "email",
"hidden": True,
"key": "string",
- "maxLength": 0,
- "minLength": 0,
+ "max_length": 0,
+ "min_length": 0,
"options": [
{
"description": "string",
@@ -110,8 +110,8 @@ def test_raw_response_create(self, client: Opencode) -> None:
"format": "email",
"hidden": True,
"key": "string",
- "maxLength": 0,
- "minLength": 0,
+ "max_length": 0,
+ "min_length": 0,
"options": [
{
"description": "string",
@@ -153,8 +153,8 @@ def test_streaming_response_create(self, client: Opencode) -> None:
"format": "email",
"hidden": True,
"key": "string",
- "maxLength": 0,
- "minLength": 0,
+ "max_length": 0,
+ "min_length": 0,
"options": [
{
"description": "string",
@@ -199,8 +199,8 @@ def test_path_params_create(self, client: Opencode) -> None:
"format": "email",
"hidden": True,
"key": "string",
- "maxLength": 0,
- "minLength": 0,
+ "max_length": 0,
+ "min_length": 0,
"options": [
{
"description": "string",
@@ -439,8 +439,8 @@ async def test_method_create(self, async_client: AsyncOpencode) -> None:
"format": "email",
"hidden": True,
"key": "string",
- "maxLength": 0,
- "minLength": 0,
+ "max_length": 0,
+ "min_length": 0,
"options": [
{
"description": "string",
@@ -478,8 +478,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpencode)
"format": "email",
"hidden": True,
"key": "string",
- "maxLength": 0,
- "minLength": 0,
+ "max_length": 0,
+ "min_length": 0,
"options": [
{
"description": "string",
@@ -519,8 +519,8 @@ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
"format": "email",
"hidden": True,
"key": "string",
- "maxLength": 0,
- "minLength": 0,
+ "max_length": 0,
+ "min_length": 0,
"options": [
{
"description": "string",
@@ -562,8 +562,8 @@ async def test_streaming_response_create(self, async_client: AsyncOpencode) -> N
"format": "email",
"hidden": True,
"key": "string",
- "maxLength": 0,
- "minLength": 0,
+ "max_length": 0,
+ "min_length": 0,
"options": [
{
"description": "string",
@@ -608,8 +608,8 @@ async def test_path_params_create(self, async_client: AsyncOpencode) -> None:
"format": "email",
"hidden": True,
"key": "string",
- "maxLength": 0,
- "minLength": 0,
+ "max_length": 0,
+ "min_length": 0,
"options": [
{
"description": "string",
diff --git a/tests/api_resources/test_credential.py b/tests/api_resources/test_credential.py
index b3e3c4f..d9ec2e7 100644
--- a/tests/api_resources/test_credential.py
+++ b/tests/api_resources/test_credential.py
@@ -26,7 +26,7 @@ def test_method_create(self, client: Opencode) -> None:
"access": "string",
"expires": 0,
"metadata": {"foo": {}},
- "methodID": "string",
+ "method_id": "string",
"refresh": "string",
"type": "oauth",
},
@@ -41,7 +41,7 @@ def test_method_create_with_all_params(self, client: Opencode) -> None:
"access": "string",
"expires": 0,
"metadata": {"foo": {}},
- "methodID": "string",
+ "method_id": "string",
"refresh": "string",
"type": "oauth",
},
@@ -59,7 +59,7 @@ def test_raw_response_create(self, client: Opencode) -> None:
"access": "string",
"expires": 0,
"metadata": {"foo": {}},
- "methodID": "string",
+ "method_id": "string",
"refresh": "string",
"type": "oauth",
},
@@ -78,7 +78,7 @@ def test_streaming_response_create(self, client: Opencode) -> None:
"access": "string",
"expires": 0,
"metadata": {"foo": {}},
- "methodID": "string",
+ "method_id": "string",
"refresh": "string",
"type": "oauth",
},
@@ -248,7 +248,7 @@ async def test_method_create(self, async_client: AsyncOpencode) -> None:
"access": "string",
"expires": 0,
"metadata": {"foo": {}},
- "methodID": "string",
+ "method_id": "string",
"refresh": "string",
"type": "oauth",
},
@@ -263,7 +263,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpencode)
"access": "string",
"expires": 0,
"metadata": {"foo": {}},
- "methodID": "string",
+ "method_id": "string",
"refresh": "string",
"type": "oauth",
},
@@ -281,7 +281,7 @@ async def test_raw_response_create(self, async_client: AsyncOpencode) -> None:
"access": "string",
"expires": 0,
"metadata": {"foo": {}},
- "methodID": "string",
+ "method_id": "string",
"refresh": "string",
"type": "oauth",
},
@@ -300,7 +300,7 @@ async def test_streaming_response_create(self, async_client: AsyncOpencode) -> N
"access": "string",
"expires": 0,
"metadata": {"foo": {}},
- "methodID": "string",
+ "method_id": "string",
"refresh": "string",
"type": "oauth",
},
diff --git a/tests/api_resources/test_generate.py b/tests/api_resources/test_generate.py
index 67da604..102ac42 100644
--- a/tests/api_resources/test_generate.py
+++ b/tests/api_resources/test_generate.py
@@ -30,7 +30,7 @@ def test_method_text_with_all_params(self, client: Opencode) -> None:
prompt="prompt",
model={
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
)
@@ -79,7 +79,7 @@ async def test_method_text_with_all_params(self, async_client: AsyncOpencode) ->
prompt="prompt",
model={
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
)
diff --git a/tests/api_resources/test_permission.py b/tests/api_resources/test_permission.py
index 59280d8..7ccdb50 100644
--- a/tests/api_resources/test_permission.py
+++ b/tests/api_resources/test_permission.py
@@ -39,7 +39,7 @@ def test_method_create_with_all_params(self, client: Opencode) -> None:
save=["string"],
source={
"id": "string",
- "messageID": "string",
+ "message_id": "string",
"type": "tool",
},
)
@@ -258,7 +258,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpencode)
save=["string"],
source={
"id": "string",
- "messageID": "string",
+ "message_id": "string",
"type": "tool",
},
)
diff --git a/tests/api_resources/test_session.py b/tests/api_resources/test_session.py
index 5bb431c..eebd445 100644
--- a/tests/api_resources/test_session.py
+++ b/tests/api_resources/test_session.py
@@ -48,7 +48,7 @@ def test_method_create_with_all_params(self, client: Opencode) -> None:
metadata={"foo": {}},
model={
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
parent_id="parentID",
@@ -684,10 +684,10 @@ def test_method_import_(self, client: Opencode) -> None:
"cost": 0,
"fork": {
"boundary": {
- "messageID": "string",
+ "message_id": "string",
"type": "before",
},
- "sessionID": "string",
+ "session_id": "string",
},
"id": "string",
"location": {
@@ -696,11 +696,11 @@ def test_method_import_(self, client: Opencode) -> None:
"metadata": {"foo": {}},
"model": {
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
"outcome": "succeeded",
- "parentID": "string",
+ "parent_id": "string",
"permissions": [
{
"action": "string",
@@ -708,7 +708,7 @@ def test_method_import_(self, client: Opencode) -> None:
"resource": "string",
}
],
- "projectID": "string",
+ "project_id": "string",
"revert": {
"files": [
{
@@ -719,8 +719,8 @@ def test_method_import_(self, client: Opencode) -> None:
"status": "added",
}
],
- "messageID": "string",
- "partID": "string",
+ "message_id": "string",
+ "part_id": "string",
"snapshot": "string",
},
"subpath": "string",
@@ -754,10 +754,10 @@ def test_method_import__with_all_params(self, client: Opencode) -> None:
"cost": 0,
"fork": {
"boundary": {
- "messageID": "string",
+ "message_id": "string",
"type": "before",
},
- "sessionID": "string",
+ "session_id": "string",
},
"id": "string",
"location": {
@@ -766,11 +766,11 @@ def test_method_import__with_all_params(self, client: Opencode) -> None:
"metadata": {"foo": {}},
"model": {
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
"outcome": "succeeded",
- "parentID": "string",
+ "parent_id": "string",
"permissions": [
{
"action": "string",
@@ -778,7 +778,7 @@ def test_method_import__with_all_params(self, client: Opencode) -> None:
"resource": "string",
}
],
- "projectID": "string",
+ "project_id": "string",
"revert": {
"files": [
{
@@ -789,8 +789,8 @@ def test_method_import__with_all_params(self, client: Opencode) -> None:
"status": "added",
}
],
- "messageID": "string",
- "partID": "string",
+ "message_id": "string",
+ "part_id": "string",
"snapshot": "string",
},
"subpath": "string",
@@ -827,10 +827,10 @@ def test_raw_response_import_(self, client: Opencode) -> None:
"cost": 0,
"fork": {
"boundary": {
- "messageID": "string",
+ "message_id": "string",
"type": "before",
},
- "sessionID": "string",
+ "session_id": "string",
},
"id": "string",
"location": {
@@ -839,11 +839,11 @@ def test_raw_response_import_(self, client: Opencode) -> None:
"metadata": {"foo": {}},
"model": {
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
"outcome": "succeeded",
- "parentID": "string",
+ "parent_id": "string",
"permissions": [
{
"action": "string",
@@ -851,7 +851,7 @@ def test_raw_response_import_(self, client: Opencode) -> None:
"resource": "string",
}
],
- "projectID": "string",
+ "project_id": "string",
"revert": {
"files": [
{
@@ -862,8 +862,8 @@ def test_raw_response_import_(self, client: Opencode) -> None:
"status": "added",
}
],
- "messageID": "string",
- "partID": "string",
+ "message_id": "string",
+ "part_id": "string",
"snapshot": "string",
},
"subpath": "string",
@@ -901,10 +901,10 @@ def test_streaming_response_import_(self, client: Opencode) -> None:
"cost": 0,
"fork": {
"boundary": {
- "messageID": "string",
+ "message_id": "string",
"type": "before",
},
- "sessionID": "string",
+ "session_id": "string",
},
"id": "string",
"location": {
@@ -913,11 +913,11 @@ def test_streaming_response_import_(self, client: Opencode) -> None:
"metadata": {"foo": {}},
"model": {
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
"outcome": "succeeded",
- "parentID": "string",
+ "parent_id": "string",
"permissions": [
{
"action": "string",
@@ -925,7 +925,7 @@ def test_streaming_response_import_(self, client: Opencode) -> None:
"resource": "string",
}
],
- "projectID": "string",
+ "project_id": "string",
"revert": {
"files": [
{
@@ -936,8 +936,8 @@ def test_streaming_response_import_(self, client: Opencode) -> None:
"status": "added",
}
],
- "messageID": "string",
- "partID": "string",
+ "message_id": "string",
+ "part_id": "string",
"snapshot": "string",
},
"subpath": "string",
@@ -1428,7 +1428,7 @@ def test_method_switch_model(self, client: Opencode) -> None:
session_id="sessionID",
model={
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
)
@@ -1440,7 +1440,7 @@ def test_raw_response_switch_model(self, client: Opencode) -> None:
session_id="sessionID",
model={
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
)
@@ -1456,7 +1456,7 @@ def test_streaming_response_switch_model(self, client: Opencode) -> None:
session_id="sessionID",
model={
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
) as response:
@@ -1475,7 +1475,7 @@ def test_path_params_switch_model(self, client: Opencode) -> None:
session_id="",
model={
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
)
@@ -1637,7 +1637,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncOpencode)
metadata={"foo": {}},
model={
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
parent_id="parentID",
@@ -2273,10 +2273,10 @@ async def test_method_import_(self, async_client: AsyncOpencode) -> None:
"cost": 0,
"fork": {
"boundary": {
- "messageID": "string",
+ "message_id": "string",
"type": "before",
},
- "sessionID": "string",
+ "session_id": "string",
},
"id": "string",
"location": {
@@ -2285,11 +2285,11 @@ async def test_method_import_(self, async_client: AsyncOpencode) -> None:
"metadata": {"foo": {}},
"model": {
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
"outcome": "succeeded",
- "parentID": "string",
+ "parent_id": "string",
"permissions": [
{
"action": "string",
@@ -2297,7 +2297,7 @@ async def test_method_import_(self, async_client: AsyncOpencode) -> None:
"resource": "string",
}
],
- "projectID": "string",
+ "project_id": "string",
"revert": {
"files": [
{
@@ -2308,8 +2308,8 @@ async def test_method_import_(self, async_client: AsyncOpencode) -> None:
"status": "added",
}
],
- "messageID": "string",
- "partID": "string",
+ "message_id": "string",
+ "part_id": "string",
"snapshot": "string",
},
"subpath": "string",
@@ -2343,10 +2343,10 @@ async def test_method_import__with_all_params(self, async_client: AsyncOpencode)
"cost": 0,
"fork": {
"boundary": {
- "messageID": "string",
+ "message_id": "string",
"type": "before",
},
- "sessionID": "string",
+ "session_id": "string",
},
"id": "string",
"location": {
@@ -2355,11 +2355,11 @@ async def test_method_import__with_all_params(self, async_client: AsyncOpencode)
"metadata": {"foo": {}},
"model": {
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
"outcome": "succeeded",
- "parentID": "string",
+ "parent_id": "string",
"permissions": [
{
"action": "string",
@@ -2367,7 +2367,7 @@ async def test_method_import__with_all_params(self, async_client: AsyncOpencode)
"resource": "string",
}
],
- "projectID": "string",
+ "project_id": "string",
"revert": {
"files": [
{
@@ -2378,8 +2378,8 @@ async def test_method_import__with_all_params(self, async_client: AsyncOpencode)
"status": "added",
}
],
- "messageID": "string",
- "partID": "string",
+ "message_id": "string",
+ "part_id": "string",
"snapshot": "string",
},
"subpath": "string",
@@ -2416,10 +2416,10 @@ async def test_raw_response_import_(self, async_client: AsyncOpencode) -> None:
"cost": 0,
"fork": {
"boundary": {
- "messageID": "string",
+ "message_id": "string",
"type": "before",
},
- "sessionID": "string",
+ "session_id": "string",
},
"id": "string",
"location": {
@@ -2428,11 +2428,11 @@ async def test_raw_response_import_(self, async_client: AsyncOpencode) -> None:
"metadata": {"foo": {}},
"model": {
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
"outcome": "succeeded",
- "parentID": "string",
+ "parent_id": "string",
"permissions": [
{
"action": "string",
@@ -2440,7 +2440,7 @@ async def test_raw_response_import_(self, async_client: AsyncOpencode) -> None:
"resource": "string",
}
],
- "projectID": "string",
+ "project_id": "string",
"revert": {
"files": [
{
@@ -2451,8 +2451,8 @@ async def test_raw_response_import_(self, async_client: AsyncOpencode) -> None:
"status": "added",
}
],
- "messageID": "string",
- "partID": "string",
+ "message_id": "string",
+ "part_id": "string",
"snapshot": "string",
},
"subpath": "string",
@@ -2490,10 +2490,10 @@ async def test_streaming_response_import_(self, async_client: AsyncOpencode) ->
"cost": 0,
"fork": {
"boundary": {
- "messageID": "string",
+ "message_id": "string",
"type": "before",
},
- "sessionID": "string",
+ "session_id": "string",
},
"id": "string",
"location": {
@@ -2502,11 +2502,11 @@ async def test_streaming_response_import_(self, async_client: AsyncOpencode) ->
"metadata": {"foo": {}},
"model": {
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
"outcome": "succeeded",
- "parentID": "string",
+ "parent_id": "string",
"permissions": [
{
"action": "string",
@@ -2514,7 +2514,7 @@ async def test_streaming_response_import_(self, async_client: AsyncOpencode) ->
"resource": "string",
}
],
- "projectID": "string",
+ "project_id": "string",
"revert": {
"files": [
{
@@ -2525,8 +2525,8 @@ async def test_streaming_response_import_(self, async_client: AsyncOpencode) ->
"status": "added",
}
],
- "messageID": "string",
- "partID": "string",
+ "message_id": "string",
+ "part_id": "string",
"snapshot": "string",
},
"subpath": "string",
@@ -3017,7 +3017,7 @@ async def test_method_switch_model(self, async_client: AsyncOpencode) -> None:
session_id="sessionID",
model={
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
)
@@ -3029,7 +3029,7 @@ async def test_raw_response_switch_model(self, async_client: AsyncOpencode) -> N
session_id="sessionID",
model={
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
)
@@ -3045,7 +3045,7 @@ async def test_streaming_response_switch_model(self, async_client: AsyncOpencode
session_id="sessionID",
model={
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
) as response:
@@ -3064,7 +3064,7 @@ async def test_path_params_switch_model(self, async_client: AsyncOpencode) -> No
session_id="",
model={
"id": "string",
- "providerID": "string",
+ "provider_id": "string",
"variant": "string",
},
)
From b837107ffcd3bbd6eaf8e5e0ff21005ddcc660f7 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 18:03:43 +0530
Subject: [PATCH 23/41] Lint and type-check with the generated code's settings;
drop unwrapped envelope models
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
pyproject.toml | 9 +++++++--
stainless.yml | 3 ---
uv.lock | 8 ++++----
3 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 2f5306c..f2f2203 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -54,7 +54,7 @@ aiohttp = [
dev = [
"dirty-equals>=0.6",
"mypy>=1.18",
- "pyright>=1.1.408",
+ "pyright==1.1.399", # the version the generated code is checked with
"pytest>=8.4",
"pytest-asyncio>=1",
"httpx-aiohttp>=0.1.9",
@@ -245,5 +245,10 @@ known-first-party = ["opencode", "tests"]
[tool.ruff.lint.per-file-ignores]
"bin/**.py" = ["T201", "T203"]
"scripts/**.py" = ["T201", "T203"]
-"tests/**.py" = ["T201", "T203"]
"examples/**.py" = ["T201", "T203"]
+# Signatures set by their callers: pytest fixtures, respx side effects, mkdocs hooks, the fake
+# server's route handlers and the PTY resize callback.
+"tests/**.py" = ["T201", "T203", "ARG"]
+"docs/**.py" = ["ARG"]
+"src/opencode/testing.py" = ["ARG002"]
+"src/opencode/_pty.py" = ["ARG001", "ARG005"]
diff --git a/stainless.yml b/stainless.yml
index b1938b4..19b0b82 100644
--- a/stainless.yml
+++ b/stainless.yml
@@ -215,7 +215,6 @@ resources:
form_string_field: "#/components/schemas/Form.StringField"
form_value: "#/components/schemas/Form.Value"
form_when: "#/components/schemas/Form.When"
- generate_text_response: "#/components/schemas/GenerateTextResponse"
installation_update_available_event: "#/components/schemas/installation.update-available"
installation_updated_event: "#/components/schemas/installation.updated"
instruction_entry_info: "#/components/schemas/InstructionEntry.Info"
@@ -371,7 +370,6 @@ resources:
session_execution_succeeded_event: "#/components/schemas/session.execution.succeeded"
session_fork_boundary: "#/components/schemas/Session.ForkBoundary"
session_forked_event: "#/components/schemas/session.forked"
- session_generate_response: "#/components/schemas/SessionGenerateResponse"
session_id: "#/components/schemas/Session.ID"
session_id1: "#/components/schemas/Session.ID_1"
session_idle_event: "#/components/schemas/session.idle"
@@ -524,7 +522,6 @@ resources:
vcs_info: "#/components/schemas/Vcs.Info"
vcs_init_not_supported_error_body: "#/components/schemas/VcsInitNotSupportedErrorEncoded"
vcs_mode: "#/components/schemas/Vcs.Mode"
- voice_session_response: "#/components/schemas/VoiceSessionResponse"
voice_transport: "#/components/schemas/VoiceTransport"
voice_transport_input: "#/components/schemas/VoiceTransportInput"
voice_web_rtc_transport: "#/components/schemas/VoiceWebRTCTransport"
diff --git a/uv.lock b/uv.lock
index b5aef28..a0591cb 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1824,7 +1824,7 @@ dev = [
{ name = "dirty-equals", specifier = ">=0.6" },
{ name = "httpx-aiohttp", specifier = ">=0.1.9" },
{ name = "mypy", specifier = ">=1.18" },
- { name = "pyright", specifier = ">=1.1.408" },
+ { name = "pyright", specifier = "==1.1.399" },
{ name = "pytest", specifier = ">=8.4" },
{ name = "pytest-asyncio", specifier = ">=1" },
{ name = "pytest-cov", specifier = ">=7" },
@@ -2327,15 +2327,15 @@ wheels = [
[[package]]
name = "pyright"
-version = "1.1.414"
+version = "1.1.399"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nodeenv" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/e1/1b/244c7b710031ada80f27e579ec20d28a2285dfc318fed0339866b1047f12/pyright-1.1.414.tar.gz", hash = "sha256:523c0a97c60da6333234955c277730c9cf4f5bd6d5399e7b7d2b0fc5d3599524", size = 4154638, upload-time = "2026-09-10T12:26:53.181Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/db/9d/d91d5f6d26b2db95476fefc772e2b9a16d54c6bd0ea6bb5c1b6d635ab8b4/pyright-1.1.399.tar.gz", hash = "sha256:439035d707a36c3d1b443aec980bc37053fbda88158eded24b8eedcf1c7b7a1b", size = 3856954, upload-time = "2025-04-10T04:40:25.703Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d7/ba/18b6e682ead424ad24bcc134339ae5d1b931cd9ae260540592a058a91279/pyright-1.1.414-py3-none-any.whl", hash = "sha256:2a6b4b3298c9eec174c5ed83bd338de6eee82df2992f3e1930e6199d381be36f", size = 6225049, upload-time = "2026-09-10T12:26:51.427Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/b5/380380c9e7a534cb1783c70c3e8ac6d1193c599650a55838d0557586796e/pyright-1.1.399-py3-none-any.whl", hash = "sha256:55f9a875ddf23c9698f24208c764465ffdfd38be6265f7faf9a176e1dc549f3b", size = 5592584, upload-time = "2025-04-10T04:40:23.502Z" },
]
[[package]]
From 8f2634913e72dd15c281ae2abc9536750368f7bc Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 18:03:50 +0530
Subject: [PATCH 24/41] chore: regenerate SDK
Ferrule-Generated-From: 811b8da2cfc11e3994a1d0f845b0280a78760a8d
---
api.md | 3 --
ferrule.lock | 13 +++---
src/opencode/resources/event.py | 6 ++-
src/opencode/types/__init__.py | 3 --
src/opencode/types/shared/__init__.py | 3 --
.../types/shared/generate_text_response.py | 41 ------------------
.../types/shared/session_generate_response.py | 41 ------------------
.../types/shared/voice_session_response.py | 42 -------------------
8 files changed, 9 insertions(+), 143 deletions(-)
delete mode 100644 src/opencode/types/shared/generate_text_response.py
delete mode 100644 src/opencode/types/shared/session_generate_response.py
delete mode 100644 src/opencode/types/shared/voice_session_response.py
diff --git a/api.md b/api.md
index 071295d..8f86e3d 100644
--- a/api.md
+++ b/api.md
@@ -76,7 +76,6 @@ from opencode.types import (
FormStringField,
FormValue,
FormWhen,
- GenerateTextResponse,
InstallationUpdateAvailableEvent,
InstallationUpdatedEvent,
InstructionEntryInfo,
@@ -211,7 +210,6 @@ from opencode.types import (
SessionExecutionSucceededEvent,
SessionForkBoundary,
SessionForkedEvent,
- SessionGenerateResponse,
SessionID,
SessionID1,
SessionIdleEvent,
@@ -344,7 +342,6 @@ from opencode.types import (
VcsFileStatus,
VcsInfo,
VcsMode,
- VoiceSessionResponse,
VoiceTransport,
VoiceTransportInput,
VoiceWebRTCTransport,
diff --git a/ferrule.lock b/ferrule.lock
index 1d05305..0df89f6 100644
--- a/ferrule.lock
+++ b/ferrule.lock
@@ -1,9 +1,9 @@
{
- "config_sha256": "15dfaee2c804c26b4cbef2629f59339943f72747b495f40afd375fb61c51c59c",
+ "config_sha256": "2fcbc78b15df7c2fc79d80d5c93fe2f98388be4906ec370440ea42d9bbee3e56",
"ferrule_version": "0.1.0",
"files": {
"AGENTS.md": "7f135189e8ab203aea9abecf018f905e395767cd38c24c2d22c3c1230c04c5e5",
- "api.md": "ebffd584aab5b25583dafdf80606b3283d711c49c844666df908d7e19d62f4fa",
+ "api.md": "fdae6e59bbdd34e11bd0fae8f378f1ec20cae61b33f345de74fc2f04834aad63",
"examples/agent/agent_list.py": "8f50db0f50024f2b1b315f8f6c08f825445172fbef62b6350751498a2e1db9a9",
"examples/agent/command_list.py": "ebfe36957eed36b76d44ac4a867c847ad1f145b0a2f6959c4157f28617b15f7d",
"examples/agent/config_get.py": "f7765272fe31984e76ecc92db552ec7c6cf1e7d046c11d2a09f4a85af1f25a89",
@@ -91,7 +91,7 @@
"src/opencode/resources/debug/__init__.py": "4b259cbe61b2fece4ebf71e4287a286f8440020daf88219543a9c7dd3038ec82",
"src/opencode/resources/debug/debug.py": "287c2d81c3b29a0166d3e9ad1c4b0e22054c385518dce277ea72e41ea22e14ec",
"src/opencode/resources/debug/location.py": "314e488618b6b3aa5d8528edad62c5bc98a1b085c3fe94a8077847ce4e8a25b1",
- "src/opencode/resources/event.py": "1726d900bda94f6a9dda4929787e96972b8d5e11492b9294c5be25e2c11af854",
+ "src/opencode/resources/event.py": "827bd57a802b9c6cfcdfc54819007ce978464915f1343af4d48e273b7b68622d",
"src/opencode/resources/experimental/__init__.py": "28feac464d3d9902214d3960dede255c9564614645757eae8faa7df956ab4877",
"src/opencode/resources/experimental/experimental.py": "23b0215a9099b6cbce704b960d93ad0ebfd1f5becef97d94bcac5f6cf4cc9a70",
"src/opencode/resources/experimental/persistent_pty.py": "524bf688f9117c384e789317b11f382af48801470de5e44a97197336177435e2",
@@ -143,7 +143,7 @@
"src/opencode/resources/voice.py": "f340f12d19f119fcd29ecdaaa23cdbb0fced2e81300e978c9825ac5959259bc3",
"src/opencode/resources/websearch.py": "c27a47f32c72e4c3b4319f655f2f954649cfb19eebc1ff7289b3e911a5f81d45",
"src/opencode/resources/worktree.py": "f91f3a3d924acf91858207804b487a7f5109689afcdcdb0cf4e938c258acbee3",
- "src/opencode/types/__init__.py": "a795fcb0f50eae5d75e8c9e0265761cb9e270b1968ade970464e95e0ca4f9598",
+ "src/opencode/types/__init__.py": "d11799f11af2202eb3df42616ba681e2514726f32bd2ec7bdddee69578b88fb7",
"src/opencode/types/agent_get_params.py": "be8560402450e9c9688692b61277624c480cb073b79844b3255347e256f2786c",
"src/opencode/types/agent_get_response.py": "50ae1d324ae34e602f676251b3a04af01f0232131ddbc4709fb4ec6d604f47eb",
"src/opencode/types/agent_list_params.py": "1475b5da3c35ac99d1e962a94b7be2e1259ff62b6732ce22bcd71d71da4237c1",
@@ -288,7 +288,7 @@
"src/opencode/types/session_synthetic_params.py": "3145b6e9f2c8a3df4ce85aac118c19af1c623b009f85994bf449f4eec3911fcc",
"src/opencode/types/session_update_params.py": "b11956698c82c646a45f86f36c8356e7cfcf6db69b71a096ddb80098ff5c99e0",
"src/opencode/types/session_view_params.py": "f8c3de01a8752c1cd41270155ef8ddba34519538469ecb08a493b9838e297862",
- "src/opencode/types/shared/__init__.py": "7499a0013c848f831de5ca14529dc97f15a10478934cd0f53c7e50ba95da24a0",
+ "src/opencode/types/shared/__init__.py": "4340a8fdd8364627606642f0645751e0c74968e80fb69ab000ad46307a776b5c",
"src/opencode/types/shared/absolute_path.py": "b1218c627f0776ab990413feab99176032e7c46ef042c30eb35dbf43629b82e8",
"src/opencode/types/shared/agent_color.py": "f30ed503c618288e3a6b018c03835e6c19df1d1ae88c1f486b5caf68780f0983",
"src/opencode/types/shared/agent_id.py": "d1972e1178e8cf3eca6ac5a1a8b1710d0b709ba41a05ba2c0bbdc6eec3e03a9c",
@@ -363,7 +363,6 @@
"src/opencode/types/shared/form_string_field.py": "8e158bc686525332065693c33aa0ab8e5e092ed174403ec6e33c3d6a4cc051ba",
"src/opencode/types/shared/form_value.py": "065a6b015488d6edcb895516ad75814f47dadc0b463df21d5918ac148276952c",
"src/opencode/types/shared/form_when.py": "88b049df4d7def2cfe65a1289cece0ca721358d9e8e93d390f95e46e326c3273",
- "src/opencode/types/shared/generate_text_response.py": "20ed317c9c617d29379ff1d9a62db03d3d5f9815d707ca39677a600f371b1c35",
"src/opencode/types/shared/installation_update_available_event.py": "e60f83bad2b2267f560ff5275e075fe249bb2d688e4648d5deec8f1ab5072bce",
"src/opencode/types/shared/installation_updated_event.py": "c8a0e05dbb2e037c65df88a33ea586cb4357502c745f5920de9588fd1f3eb7d7",
"src/opencode/types/shared/instruction_entry_info.py": "23ae698497a785059756d432b2c5b52e6346e7e9eeaf52f1037b1733e98f1841",
@@ -498,7 +497,6 @@
"src/opencode/types/shared/session_execution_succeeded_event.py": "766518720e2c5fd091b0bd65d75c8eabf55f105a9a089942a68906e5f55bb541",
"src/opencode/types/shared/session_fork_boundary.py": "305cf573f37df3b975d051dd5dd3613d93d39e35c153c85ef769287c479385e7",
"src/opencode/types/shared/session_forked_event.py": "257f04c11bddad4689fc4019c6cc191bffc4853f1d5dacdc7cf1cdbd67fe5126",
- "src/opencode/types/shared/session_generate_response.py": "3cba04a5482c45a04032c3552d792e3be0314c3229b3c86966cb3a47936cfc18",
"src/opencode/types/shared/session_id.py": "71d5ec00fee3567bb3feb15ae61aa545c2d9c791b9d2fa83de26adb7a70148e8",
"src/opencode/types/shared/session_id1.py": "abe58a8620b096ff92564e084842dc3c860e6c570f508cc867c627149c7921fa",
"src/opencode/types/shared/session_idle_event.py": "d0315d5011638cbf5d47c0b7d4bb69ed7e5591491c07dcf2f8f788100f6824ce",
@@ -631,7 +629,6 @@
"src/opencode/types/shared/vcs_file_status.py": "23dbe802c507e99a55009b2f53f2563553e136e27386789927f80627bb4baaca",
"src/opencode/types/shared/vcs_info.py": "1c3bbe44717c3d36d3eb873908b2c2b6d617195ffff2e452aa51c33c431a08ee",
"src/opencode/types/shared/vcs_mode.py": "ef82eafc81a30ca284c7cbdd05223162f9c1e5791f86aa446f9dbc5129012405",
- "src/opencode/types/shared/voice_session_response.py": "3321ed9b071786f7888e192172a5c5e8a4a0d466b28be722dfe22efedf348c9e",
"src/opencode/types/shared/voice_transport.py": "dea19fbc791abfb99dcc6570fa164c08471018ab5390a2038083bda9c3a9f111",
"src/opencode/types/shared/voice_transport_input.py": "e9e1e0115a966c16fff41c2b7132530eb45399a93809ab8a9fbf82df1887bf95",
"src/opencode/types/shared/voice_web_rtc_transport.py": "44b0d70c422e77043dda147db64f49eb748855df23f5a18a66379c46df43635c",
diff --git a/src/opencode/resources/event.py b/src/opencode/resources/event.py
index 3f49558..9e87c4a 100644
--- a/src/opencode/resources/event.py
+++ b/src/opencode/resources/event.py
@@ -2,6 +2,8 @@
from __future__ import annotations
+from typing import Any, cast
+
import httpx
from .._types import Body, Query, Headers, NotGiven, not_given
@@ -60,7 +62,7 @@ def subscribe(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=Event,
+ cast_to=cast(Any, Event), # Union types cannot be passed in as arguments in the type system
stream=True,
stream_cls=Stream[Event],
)
@@ -106,7 +108,7 @@ async def subscribe(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=Event,
+ cast_to=cast(Any, Event), # Union types cannot be passed in as arguments in the type system
stream=True,
stream_cls=AsyncStream[Event],
)
diff --git a/src/opencode/types/__init__.py b/src/opencode/types/__init__.py
index 64ecc7d..209911d 100644
--- a/src/opencode/types/__init__.py
+++ b/src/opencode/types/__init__.py
@@ -213,7 +213,6 @@
ConfigModelReasoning as ConfigModelReasoning,
ConfigReferenceLocal as ConfigReferenceLocal,
FormMultiselectField as FormMultiselectField,
- GenerateTextResponse as GenerateTextResponse,
InstructionEntryInfo as InstructionEntryInfo,
IntegrationAttemptID as IntegrationAttemptID,
IntegrationEnvMethod as IntegrationEnvMethod,
@@ -231,7 +230,6 @@
SessionStatsActivity as SessionStatsActivity,
SessionStatusUpdated as SessionStatusUpdated,
TuiPromptAppendEvent as TuiPromptAppendEvent,
- VoiceSessionResponse as VoiceSessionResponse,
VoiceWebRTCTransport as VoiceWebRTCTransport,
WorktreeUpdatedEvent as WorktreeUpdatedEvent,
LocationShutdownEvent as LocationShutdownEvent,
@@ -286,7 +284,6 @@
ModelsDevRefreshedEvent as ModelsDevRefreshedEvent,
PersistentPtyAddedEvent as PersistentPtyAddedEvent,
PersistentPtyReadResult as PersistentPtyReadResult,
- SessionGenerateResponse as SessionGenerateResponse,
SessionInboxMovePayload as SessionInboxMovePayload,
SessionInboxUserPayload as SessionInboxUserPayload,
SessionMessageAssistant as SessionMessageAssistant,
diff --git a/src/opencode/types/shared/__init__.py b/src/opencode/types/shared/__init__.py
index 53bd0a0..820f03b 100644
--- a/src/opencode/types/shared/__init__.py
+++ b/src/opencode/types/shared/__init__.py
@@ -209,7 +209,6 @@
from .config_reference_local import ConfigReferenceLocal as ConfigReferenceLocal
from .config_web_search_info import ConfigWebSearchInfo as ConfigWebSearchInfo
from .form_multiselect_field import FormMultiselectField as FormMultiselectField
-from .generate_text_response import GenerateTextResponse as GenerateTextResponse
from .instruction_entry_info import InstructionEntryInfo as InstructionEntryInfo
from .integration_attempt_id import IntegrationAttemptID as IntegrationAttemptID
from .integration_env_method import IntegrationEnvMethod as IntegrationEnvMethod
@@ -228,7 +227,6 @@
from .session_message_system import SessionMessageSystem as SessionMessageSystem
from .session_stats_activity import SessionStatsActivity as SessionStatsActivity
from .session_status_updated import SessionStatusUpdated as SessionStatusUpdated
-from .voice_session_response import VoiceSessionResponse as VoiceSessionResponse
from .worktree_updated_event import WorktreeUpdatedEvent as WorktreeUpdatedEvent
from .location_shutdown_event import LocationShutdownEvent as LocationShutdownEvent
from .persistent_pty_snapshot import PersistentPtySnapshot as PersistentPtySnapshot
@@ -273,7 +271,6 @@
from .integration_o_auth_method import IntegrationOAuthMethod as IntegrationOAuthMethod
from .integration_updated_event import IntegrationUpdatedEvent as IntegrationUpdatedEvent
from .persistent_pty_read_lines import PersistentPtyReadLines as PersistentPtyReadLines
-from .session_generate_response import SessionGenerateResponse as SessionGenerateResponse
from .session_message_assistant import SessionMessageAssistant as SessionMessageAssistant
from .session_message_synthetic import SessionMessageSynthetic as SessionMessageSynthetic
from .session_messages_response import SessionMessagesResponse as SessionMessagesResponse
diff --git a/src/opencode/types/shared/generate_text_response.py b/src/opencode/types/shared/generate_text_response.py
deleted file mode 100644
index 913cc75..0000000
--- a/src/opencode/types/shared/generate_text_response.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from ..._models import BaseModel
-
-__all__ = ["GenerateTextResponse", "Data"]
-
-
-class Data(BaseModel):
- text: str
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
-
-
-class GenerateTextResponse(BaseModel):
- data: Data
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/session_generate_response.py b/src/opencode/types/shared/session_generate_response.py
deleted file mode 100644
index 7fe7196..0000000
--- a/src/opencode/types/shared/session_generate_response.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from ..._models import BaseModel
-
-__all__ = ["SessionGenerateResponse", "Data"]
-
-
-class Data(BaseModel):
- text: str
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
-
-
-class SessionGenerateResponse(BaseModel):
- data: Data
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
diff --git a/src/opencode/types/shared/voice_session_response.py b/src/opencode/types/shared/voice_session_response.py
deleted file mode 100644
index b4e3f1b..0000000
--- a/src/opencode/types/shared/voice_session_response.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# File generated from our OpenAPI spec by Ferrule. See CONTRIBUTING.md for details.
-
-from typing import TYPE_CHECKING, Dict
-
-from pydantic import Field as FieldInfo
-
-from ..._models import BaseModel
-from .voice_transport import VoiceTransport
-
-__all__ = ["VoiceSessionResponse", "Data"]
-
-
-class Data(BaseModel):
- transport: VoiceTransport
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
-
-
-class VoiceSessionResponse(BaseModel):
- data: Data
-
- if TYPE_CHECKING:
- # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a
- # value to this field, so for compatibility we avoid doing it at runtime.
- __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
-
- # Stub to indicate that arbitrary properties are accepted.
- # To access properties that are not valid identifiers you can use `getattr`, e.g.
- # `getattr(obj, '$type')`
- def __getattr__(self, attr: str) -> object: ...
- else:
- __pydantic_extra__: Dict[str, object]
From 1aa3a80c0f4e73264f2ef1c1530e5b5579ca80b9 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 18:04:05 +0530
Subject: [PATCH 25/41] Format and lint the custom code with the generated
code's settings
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
docs/hooks/types_reference.py | 6 +--
examples/quickstart.py | 2 +-
examples/stream_events.py | 2 +-
examples/terminal.py | 4 +-
scripts/next_version.py | 2 +-
src/opencode/__init__.py | 14 ++---
src/opencode/_errors.py | 8 +--
src/opencode/_events.py | 22 ++++----
src/opencode/_pty.py | 17 +++---
src/opencode/_rpc.py | 19 ++++---
src/opencode/_server.py | 32 +++++++-----
src/opencode/_service.py | 22 ++++----
src/opencode/lib/_client.py | 26 +++++-----
src/opencode/lib/_ready.py | 4 +-
src/opencode/lib/_resources.py | 12 ++---
src/opencode/lib/_session.py | 16 +++---
src/opencode/lib/_support.py | 11 ++--
src/opencode/service.py | 18 +++----
src/opencode/testing.py | 86 +++++++++++++++++--------------
tests/custom/conftest.py | 6 +--
tests/custom/fake_service.py | 7 ++-
tests/custom/test_events.py | 21 ++++----
tests/custom/test_helpers.py | 11 ++--
tests/custom/test_live.py | 8 +--
tests/custom/test_pagination.py | 4 +-
tests/custom/test_pty.py | 12 ++---
tests/custom/test_rpc.py | 8 +--
tests/custom/test_service.py | 10 ++--
tests/custom/test_service_live.py | 2 +-
29 files changed, 215 insertions(+), 197 deletions(-)
diff --git a/docs/hooks/types_reference.py b/docs/hooks/types_reference.py
index e77a272..674dc09 100644
--- a/docs/hooks/types_reference.py
+++ b/docs/hooks/types_reference.py
@@ -8,11 +8,11 @@
from __future__ import annotations
-import ast
import re
-from collections import defaultdict
-from pathlib import Path
+import ast
from typing import TYPE_CHECKING, Any
+from pathlib import Path
+from collections import defaultdict
if TYPE_CHECKING:
from mkdocs.structure.files import Files
diff --git a/examples/quickstart.py b/examples/quickstart.py
index 9229e30..8f16b9a 100644
--- a/examples/quickstart.py
+++ b/examples/quickstart.py
@@ -8,7 +8,7 @@
import sys
from opencode import OpencodeServer
-from opencode.types import SessionMessageAssistant, SessionMessageUser
+from opencode.types import SessionMessageUser, SessionMessageAssistant
def main(directory: str, prompt: str) -> None:
diff --git a/examples/stream_events.py b/examples/stream_events.py
index f141148..1d336f6 100644
--- a/examples/stream_events.py
+++ b/examples/stream_events.py
@@ -8,7 +8,7 @@
import asyncio
from opencode import AsyncOpencode
-from opencode.types import SessionTextDeltaEvent, UnknownEvent
+from opencode.types import UnknownEvent, SessionTextDeltaEvent
async def main() -> None:
diff --git a/examples/terminal.py b/examples/terminal.py
index 66131f9..32e3229 100644
--- a/examples/terminal.py
+++ b/examples/terminal.py
@@ -8,12 +8,12 @@
from __future__ import annotations
import os
+import sys
+import tty
import shutil
import signal
-import sys
import termios
import threading
-import tty
from typing import Any
from opencode import Opencode, PtyConnection
diff --git a/scripts/next_version.py b/scripts/next_version.py
index 5aa7dd7..f457ae8 100644
--- a/scripts/next_version.py
+++ b/scripts/next_version.py
@@ -16,8 +16,8 @@
from __future__ import annotations
import re
-import subprocess
import sys
+import subprocess
RELEASE_PATHS = ("src/", "pyproject.toml", "spec/")
BREAKING = re.compile(r"^[a-z]+(\([^)]*\))?!:", re.IGNORECASE)
diff --git a/src/opencode/__init__.py b/src/opencode/__init__.py
index 2b19f0a..bc89240 100644
--- a/src/opencode/__init__.py
+++ b/src/opencode/__init__.py
@@ -104,12 +104,11 @@
# Custom code, kept by `ferrule regenerate`: the clients with this SDK's own features (lib/_client.py)
# and the public names of the hand-written modules.
from . import service # noqa: E402
+from ._pty import PtyConnection, AsyncPtyConnection, PtyConnectionError # noqa: E402
from ._rpc import ( # noqa: E402
- RpcMethod,
RpcClient,
RpcEvents,
- rpc_event,
- rpc_method,
+ RpcMethod,
RpcCallError,
RpcDefinition,
AsyncRpcClient,
@@ -117,24 +116,25 @@
RpcEventPayload,
RpcEventDefinition,
RpcCallInternalError,
+ rpc_event,
+ rpc_method,
)
-from ._pty import PtyConnection, AsyncPtyConnection, PtyConnectionError # noqa: E402
from ._errors import * # noqa: E402, F403
from ._errors import __all__ as _errors_all # noqa: E402
from ._events import ( # noqa: E402
+ SUBSCRIBER_CAPACITY,
EventHub,
Reconnect,
Subscription,
AsyncEventHub,
- session_id_of,
AsyncSubscription,
EventOverflowError,
- SUBSCRIBER_CAPACITY,
+ session_id_of,
)
from ._server import OpencodeServer, ServerStartError, AsyncOpencodeServer # noqa: E402
from ._service import Endpoint, ServiceError # noqa: E402
-from .lib._client import Opencode as Opencode, AsyncOpencode as AsyncOpencode # noqa: E402
from .pagination import SyncCursorPage, AsyncCursorPage # noqa: E402
+from .lib._client import Opencode as Opencode, AsyncOpencode as AsyncOpencode # noqa: E402
from ._base_client import AsyncPaginator # noqa: E402
Client = Opencode
diff --git a/src/opencode/_errors.py b/src/opencode/_errors.py
index 5261c34..66a4756 100644
--- a/src/opencode/_errors.py
+++ b/src/opencode/_errors.py
@@ -13,16 +13,16 @@
import pydantic
+from ._models import BaseModel
from ._exceptions import (
+ ConflictError,
+ NotFoundError,
APIStatusError,
- AuthenticationError,
BadRequestError,
- ConflictError,
+ AuthenticationError,
InternalServerError,
- NotFoundError,
PermissionDeniedError,
)
-from ._models import BaseModel
if TYPE_CHECKING:
import httpx
diff --git a/src/opencode/_events.py b/src/opencode/_events.py
index 4ae9e03..915a567 100644
--- a/src/opencode/_events.py
+++ b/src/opencode/_events.py
@@ -19,28 +19,28 @@
from __future__ import annotations
-import asyncio
-import collections
-import contextlib
-import logging
import random
import socket
+import asyncio
+import logging
import threading
-from dataclasses import dataclass
+import contextlib
+import collections
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast
+from dataclasses import dataclass
+from typing_extensions import Self
import anyio
-from typing_extensions import Self
-from ._exceptions import APIConnectionError, APIStatusError, OpencodeError
+from ._exceptions import OpencodeError, APIStatusError, APIConnectionError
if TYPE_CHECKING:
- from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
from types import TracebackType
+ from collections.abc import Callable, Iterator, Awaitable, AsyncIterator
- from .lib._client import AsyncOpencode, Opencode
- from ._streaming import AsyncStream, Stream
from .types import Event, ServerConnectedEvent
+ from ._streaming import Stream, AsyncStream
+ from .lib._client import Opencode, AsyncOpencode
__all__ = [
"SUBSCRIBER_CAPACITY",
@@ -176,6 +176,8 @@ def _interrupt(stream: Stream[Event]) -> None:
if isinstance(sock, socket.socket):
with contextlib.suppress(OSError):
sock.shutdown(socket.SHUT_RDWR)
+
+
"""Raised by :meth:`AsyncSubscription.get` once the stream is over."""
# Completion states of one subscriber.
diff --git a/src/opencode/_pty.py b/src/opencode/_pty.py
index eb9f667..38120c4 100644
--- a/src/opencode/_pty.py
+++ b/src/opencode/_pty.py
@@ -26,28 +26,27 @@
from __future__ import annotations
import json
-import struct
import uuid
+import struct
from typing import TYPE_CHECKING, Any, Literal, cast
from urllib.parse import urlencode
-
from typing_extensions import Self
-from ._exceptions import OpencodeError
-from ._resource import AsyncAPIResource, SyncAPIResource
from ._types import NOT_GIVEN, NotGiven
-from .lib._support import default_location, encode_path_segment, encode_query
+from ._resource import SyncAPIResource, AsyncAPIResource
+from ._exceptions import OpencodeError
+from .lib._support import encode_query, default_location, encode_path_segment
if TYPE_CHECKING:
- from collections.abc import AsyncIterator, Awaitable, Callable, Iterator, Mapping
from types import TracebackType
+ from collections.abc import Mapping, Callable, Iterator, Awaitable, AsyncIterator
import httpx
- from websockets.asyncio.client import ClientConnection as AsyncWebSocket
from websockets.sync.client import ClientConnection as SyncWebSocket
+ from websockets.asyncio.client import ClientConnection as AsyncWebSocket
- from .lib._support import Location
from .types import PtyID
+ from .lib._support import Location
__all__ = [
"AsyncPersistentPtyAttachMixin",
@@ -388,8 +387,8 @@ def _open_sync(url: str, headers: dict[str, str], open_timeout: float | None) ->
async def _open_async(url: str, headers: dict[str, str], open_timeout: float | None) -> AsyncWebSocket:
try:
- from websockets.asyncio.client import connect
from websockets.exceptions import InvalidHandshake
+ from websockets.asyncio.client import connect
except ImportError:
raise _missing() from None
try:
diff --git a/src/opencode/_rpc.py b/src/opencode/_rpc.py
index 3228403..2173009 100644
--- a/src/opencode/_rpc.py
+++ b/src/opencode/_rpc.py
@@ -33,28 +33,27 @@ class Counter(RpcDefinition, id="counter"):
from __future__ import annotations
+from typing import TYPE_CHECKING, Any, Generic, TypeVar, ClassVar, cast, overload
from dataclasses import dataclass
-from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar, cast, overload
+from typing_extensions import Self
import pydantic
-from typing_extensions import Self
+from ._types import NOT_GIVEN, NotGiven
from ._errors import RpcError, RpcInternalError
from ._exceptions import OpencodeError
-from ._types import NOT_GIVEN, NotGiven
from .lib._support import type_adapter, validation_summary
-from .resources.rpc import AsyncRpcResource, RpcResource
+from .resources.rpc import RpcResource, AsyncRpcResource
if TYPE_CHECKING:
- from collections.abc import Mapping
from types import TracebackType
+ from collections.abc import Mapping
- from .lib._client import AsyncOpencode, Opencode
- from ._events import AsyncSubscription, Subscription
- from ._types import Headers
- from .lib._support import Timeout
- from .lib._support import Location
from .types import RpcEvent
+ from ._types import Headers
+ from ._events import Subscription, AsyncSubscription
+ from .lib._client import Opencode, AsyncOpencode
+ from .lib._support import Timeout, Location
__all__ = [
"AsyncRpcClient",
diff --git a/src/opencode/_server.py b/src/opencode/_server.py
index 2a193a9..6d48883 100644
--- a/src/opencode/_server.py
+++ b/src/opencode/_server.py
@@ -2,31 +2,31 @@
from __future__ import annotations
-import collections
-import contextlib
-import json
import os
+import sys
+import json
+import time
import queue
-import secrets
import shutil
-import subprocess
-import sys
+import secrets
import threading
-import time
-from pathlib import Path
+import contextlib
+import subprocess
+import collections
from typing import IO, TYPE_CHECKING, Any, cast
+from pathlib import Path
+from typing_extensions import Self
-import anyio.to_thread
import httpx
-from typing_extensions import Self
+import anyio.to_thread
-from .lib._client import AsyncOpencode, Opencode
from ._exceptions import OpencodeError
+from .lib._client import Opencode, AsyncOpencode
from .lib._support import DEFAULT_USERNAME
if TYPE_CHECKING:
- from collections.abc import Mapping
from types import TracebackType
+ from collections.abc import Mapping
from .lib._support import Timeout
@@ -120,14 +120,18 @@ def running(self) -> bool:
def client(self, *, directory: str | os.PathLike[str] | None = None, timeout: Timeout | None = None) -> Opencode:
"""Returns a sync client connected to this server."""
kwargs: dict[str, Any] = {} if timeout is None else {"timeout": timeout}
- return Opencode(base_url=self.url, password=self.password, username=DEFAULT_USERNAME, directory=directory, **kwargs)
+ return Opencode(
+ base_url=self.url, password=self.password, username=DEFAULT_USERNAME, directory=directory, **kwargs
+ )
def async_client(
self, *, directory: str | os.PathLike[str] | None = None, timeout: Timeout | None = None
) -> AsyncOpencode:
"""Returns an async client connected to this server."""
kwargs: dict[str, Any] = {} if timeout is None else {"timeout": timeout}
- return AsyncOpencode(base_url=self.url, password=self.password, username=DEFAULT_USERNAME, directory=directory, **kwargs)
+ return AsyncOpencode(
+ base_url=self.url, password=self.password, username=DEFAULT_USERNAME, directory=directory, **kwargs
+ )
def _command(self) -> list[str]:
executable = shutil.which(self.binary)
diff --git a/src/opencode/_service.py b/src/opencode/_service.py
index abfdd68..d767d6d 100644
--- a/src/opencode/_service.py
+++ b/src/opencode/_service.py
@@ -12,22 +12,22 @@
from __future__ import annotations
-import base64
-import contextlib
-import json
-import logging
import os
-import shutil
-import signal
-import subprocess
import sys
-import threading
+import json
import time
import uuid
-from collections.abc import Callable, Mapping, Sequence
-from dataclasses import dataclass, field
-from pathlib import Path
+import base64
+import shutil
+import signal
+import logging
+import threading
+import contextlib
+import subprocess
from typing import Any, Literal, TypeAlias, cast
+from pathlib import Path
+from dataclasses import field, dataclass
+from collections.abc import Mapping, Callable, Sequence
import httpx
from anyio import sleep as _async_sleep
diff --git a/src/opencode/lib/_client.py b/src/opencode/lib/_client.py
index 1a9419b..e3b065a 100644
--- a/src/opencode/lib/_client.py
+++ b/src/opencode/lib/_client.py
@@ -17,31 +17,31 @@
import os
from typing import TYPE_CHECKING, Any, cast
+from typing_extensions import Self, override
import httpx
-from typing_extensions import Self, override
-from .._client import AsyncOpencode as _AsyncOpencode, Opencode as _Opencode
+from .._rpc import TypedRpcResource, AsyncTypedRpcResource
+from .._types import NOT_GIVEN, NotGiven
+from .._client import Opencode as _Opencode, AsyncOpencode as _AsyncOpencode
from .._compat import cached_property
from .._errors import tagged_status_error
-from .._events import AsyncEventHub, EventHub
-from .._exceptions import APIStatusError
-from .._rpc import AsyncTypedRpcResource, TypedRpcResource
+from .._events import EventHub, AsyncEventHub
+from ._support import DEFAULT_USERNAME
from .._service import (
+ Version,
EnsureReason,
ServiceError,
- Version,
- async_discover,
- async_ensure,
- discover as discover_service,
ensure as ensure_service,
+ discover as discover_service,
+ async_ensure,
+ async_discover,
)
-from .._types import NOT_GIVEN, NotGiven
-from ._resources import AsyncExperimental, AsyncPty, AsyncSession, Experimental, Pty, Session
-from ._support import DEFAULT_USERNAME
+from ._resources import Pty, Session, AsyncPty, AsyncSession, Experimental, AsyncExperimental
+from .._exceptions import APIStatusError
if TYPE_CHECKING:
- from collections.abc import Callable, Iterable, Mapping, Sequence
+ from collections.abc import Mapping, Callable, Iterable, Sequence
from .._models import FinalRequestOptions
from ..testing import FakeServer
diff --git a/src/opencode/lib/_ready.py b/src/opencode/lib/_ready.py
index 7ddd9cb..b4e42d8 100644
--- a/src/opencode/lib/_ready.py
+++ b/src/opencode/lib/_ready.py
@@ -17,8 +17,8 @@
if TYPE_CHECKING:
from collections.abc import Iterable
- from ._client import AsyncOpencode, Opencode
- from ..types import AgentListResponse, McpListResponse
+ from ..types import McpListResponse, AgentListResponse
+ from ._client import Opencode, AsyncOpencode
__all__ = ["POLL_INTERVAL", "async_wait_until_ready", "wait_until_ready"]
diff --git a/src/opencode/lib/_resources.py b/src/opencode/lib/_resources.py
index ffd6874..2c04fc3 100644
--- a/src/opencode/lib/_resources.py
+++ b/src/opencode/lib/_resources.py
@@ -7,13 +7,13 @@
from __future__ import annotations
+from .._pty import PtyAttachMixin, AsyncPtyAttachMixin, PersistentPtyAttachMixin, AsyncPersistentPtyAttachMixin
from .._compat import cached_property
-from .._pty import AsyncPersistentPtyAttachMixin, AsyncPtyAttachMixin, PersistentPtyAttachMixin, PtyAttachMixin
-from ..resources.experimental.experimental import AsyncExperimentalResource, ExperimentalResource
-from ..resources.experimental.persistent_pty import AsyncPersistentPtyResource, PersistentPtyResource
-from ..resources.pty.pty import AsyncPtyResource, PtyResource
-from ..resources.session.session import AsyncSessionResource, SessionResource
-from ._session import AsyncSessionHelpers, SessionHelpers
+from ._session import SessionHelpers, AsyncSessionHelpers
+from ..resources.pty.pty import PtyResource, AsyncPtyResource
+from ..resources.session.session import SessionResource, AsyncSessionResource
+from ..resources.experimental.experimental import ExperimentalResource, AsyncExperimentalResource
+from ..resources.experimental.persistent_pty import PersistentPtyResource, AsyncPersistentPtyResource
__all__ = [
"AsyncExperimental",
diff --git a/src/opencode/lib/_session.py b/src/opencode/lib/_session.py
index 77cd8c0..c23037b 100644
--- a/src/opencode/lib/_session.py
+++ b/src/opencode/lib/_session.py
@@ -7,29 +7,29 @@
from __future__ import annotations
import contextlib
-from dataclasses import dataclass, field
from typing import TYPE_CHECKING, cast
+from dataclasses import field, dataclass
import anyio
-from .._resource import AsyncAPIResource, SyncAPIResource
-from .._types import NOT_GIVEN, NotGiven, Timeout
+from .._types import NOT_GIVEN, Timeout, NotGiven
+from .._resource import SyncAPIResource, AsyncAPIResource
if TYPE_CHECKING:
from collections.abc import Sequence
- from ..resources.session import AsyncSessionResource, SessionResource
from ..types import (
AgentID,
ModelRef,
- PromptInputFileAttachment,
SessionID,
SessionInfo,
- SessionMessageAssistant,
+ TokenUsageInfo,
SessionMessageInfo,
SessionStructuredError,
- TokenUsageInfo,
+ SessionMessageAssistant,
+ PromptInputFileAttachment,
)
+ from ..resources.session import SessionResource, AsyncSessionResource
__all__ = ["AsyncSessionHelpers", "RunResult", "SessionHelpers"]
@@ -74,7 +74,7 @@ def outcome(self) -> str | None:
@property
def error(self) -> SessionStructuredError | None:
"""Why the run failed, from its idle marker or last assistant message, or ``None``."""
- from ..types import SessionMessageAssistant, SessionMessageIdle
+ from ..types import SessionMessageIdle, SessionMessageAssistant
for message in reversed(self.messages):
if isinstance(message, (SessionMessageIdle, SessionMessageAssistant)) and message.error is not None:
diff --git a/src/opencode/lib/_support.py b/src/opencode/lib/_support.py
index dc2ce50..50eeacb 100644
--- a/src/opencode/lib/_support.py
+++ b/src/opencode/lib/_support.py
@@ -7,20 +7,19 @@
from __future__ import annotations
+import enum
import base64
import datetime as dt
-import enum
import functools
+from typing import Any, Union, Optional, cast
+from urllib.parse import quote
from collections.abc import Mapping, Sequence
-from typing import Any, Optional, Union, cast
-
from typing_extensions import TypedDict
-from urllib.parse import quote
import httpx
import pydantic
-from .._types import NotGiven, Omit
+from .._types import Omit, NotGiven
__all__ = [
"DEFAULT_USERNAME",
@@ -38,11 +37,13 @@
DEFAULT_USERNAME = "opencode"
"""The Basic auth user name the OpenCode server expects."""
+
class Location(TypedDict, total=False):
"""Where a request runs on the server, for example ``Location(directory="/repo")``."""
directory: Optional[str]
+
Timeout = Union[float, httpx.Timeout, None]
"""A timeout in seconds, an ``httpx.Timeout``, or ``None`` for none."""
diff --git a/src/opencode/service.py b/src/opencode/service.py
index 3c6c109..be50bc3 100644
--- a/src/opencode/service.py
+++ b/src/opencode/service.py
@@ -16,20 +16,20 @@
from __future__ import annotations
from ._service import (
- BasicAuth,
+ PtyMode,
+ Version,
Endpoint,
+ BasicAuth,
EnsureReason,
- PtyMode,
ServiceError,
- Version,
- async_discover,
- async_ensure,
- async_stop,
- default_file,
- discover,
+ stop,
ensure,
headers,
- stop,
+ discover,
+ async_stop,
+ async_ensure,
+ default_file,
+ async_discover,
)
__all__ = [
diff --git a/src/opencode/testing.py b/src/opencode/testing.py
index 5085cba..6ee2627 100644
--- a/src/opencode/testing.py
+++ b/src/opencode/testing.py
@@ -21,38 +21,38 @@
from __future__ import annotations
-import itertools
-import json
import os
import re
+import json
import time
-from collections.abc import Callable, Iterable, Mapping
-from dataclasses import dataclass
+import itertools
from typing import Any, TypeVar
+from dataclasses import dataclass
+from collections.abc import Mapping, Callable, Iterable
import httpx
-from ._models import BaseModel
from .types import (
+ ModelRef,
AgentInfo,
- AgentListResponse,
- LocationPublicRef,
- McpListResponse,
McpServer,
- ModelRef,
- ServerConnectedEvent,
ServerInfo,
- SessionCreatedEvent,
+ SessionInfo,
+ TokenUsageInfo,
+ McpListResponse,
SessionIdleEvent,
SessionInboxUser,
- SessionInfo,
- SessionInterruptResponse,
- SessionMessageAssistant,
- SessionMessageAssistantText,
+ AgentListResponse,
+ LocationPublicRef,
SessionMessageIdle,
SessionMessageUser,
- TokenUsageInfo,
+ SessionCreatedEvent,
+ ServerConnectedEvent,
+ SessionMessageAssistant,
+ SessionInterruptResponse,
+ SessionMessageAssistantText,
)
+from ._models import BaseModel
__all__ = ["DEFAULT_MODEL", "FakeServer"]
@@ -220,7 +220,8 @@ def _run(self, session: SessionInfo, text: str) -> None:
input_tokens, output_tokens = self.tokens
usage = {"input": input_tokens, "output": output_tokens, "reasoning": 0, "cache": {"read": 0, "write": 0}}
self.messages[session.id] += [
- _model(SessionMessageAssistant,
+ _model(
+ SessionMessageAssistant,
{
"id": self._id("msg"),
"type": "assistant",
@@ -231,39 +232,43 @@ def _run(self, session: SessionInfo, text: str) -> None:
"finish": "stop",
"cost": self.cost,
"tokens": usage,
- }
+ },
),
- _model(SessionMessageIdle,
- {"id": self._id("msg"), "type": "idle", "time": {"created": now}, "outcome": "succeeded"}
+ _model(
+ SessionMessageIdle,
+ {"id": self._id("msg"), "type": "idle", "time": {"created": now}, "outcome": "succeeded"},
),
]
session.cost += self.cost
- session.tokens = _model(TokenUsageInfo,
+ session.tokens = _model(
+ TokenUsageInfo,
{
"input": session.tokens.input + input_tokens,
"output": session.tokens.output + output_tokens,
"reasoning": session.tokens.reasoning,
"cache": session.tokens.cache.to_dict(),
- }
+ },
)
session.outcome = "succeeded"
session.time.updated = session.time.idle = now
self._record(
- _model(SessionIdleEvent,
- {"id": self._id("evt"), "created": now, "type": "session.idle", "data": {"sessionID": session.id}}
+ _model(
+ SessionIdleEvent,
+ {"id": self._id("evt"), "created": now, "type": "session.idle", "data": {"sessionID": session.id}},
)
)
# -- routes ---------------------------------------------------------------------------------------------
def _server_info(self, request: httpx.Request, match: re.Match[str]) -> httpx.Response:
- info = _model(ServerInfo,
+ info = _model(
+ ServerInfo,
{
"version": "fake",
"pid": os.getpid(),
"urls": [str(request.url.copy_with(path="/"))],
"paths": {"tmp": "/tmp"},
- }
+ },
)
return _json(info)
@@ -271,7 +276,8 @@ def _agent_list(self, request: httpx.Request, match: re.Match[str]) -> httpx.Res
self._polls["agent"] += 1
visible = [agent for agent, after in self._agents if self._polls["agent"] > after]
agents = [
- _model(AgentInfo,
+ _model(
+ AgentInfo,
{
"id": agent,
"name": agent,
@@ -279,7 +285,7 @@ def _agent_list(self, request: httpx.Request, match: re.Match[str]) -> httpx.Res
"hidden": False,
"request": {"settings": {}, "headers": {}, "body": {}},
"permissions": [],
- }
+ },
)
for agent in dict.fromkeys(visible)
]
@@ -288,8 +294,8 @@ def _agent_list(self, request: httpx.Request, match: re.Match[str]) -> httpx.Res
def _mcp_list(self, request: httpx.Request, match: re.Match[str]) -> httpx.Response:
self._polls["mcp"] += 1
servers = [
- _model(McpServer,
- {"name": name, "status": _mcp_status(status if self._polls["mcp"] > after else "pending")}
+ _model(
+ McpServer, {"name": name, "status": _mcp_status(status if self._polls["mcp"] > after else "pending")}
)
for name, (status, after) in self._mcp.items()
]
@@ -301,7 +307,8 @@ def _session_create(self, request: httpx.Request, match: re.Match[str]) -> httpx
session_id = body.get("id") or self._id("ses")
if session_id in self.sessions:
raise _ErrorResponse(409, {"_tag": "ConflictError", "message": f"Session already exists: {session_id}"})
- session = _model(SessionInfo,
+ session = _model(
+ SessionInfo,
{
"id": session_id,
"parentID": body.get("parentID"),
@@ -314,13 +321,14 @@ def _session_create(self, request: httpx.Request, match: re.Match[str]) -> httpx
"title": body.get("title"),
"metadata": body.get("metadata"),
"location": self._location.to_dict(),
- }
+ },
)
self.sessions[session.id] = session
self.messages[session.id] = []
self.inbox[session.id] = []
self._record(
- _model(SessionCreatedEvent,
+ _model(
+ SessionCreatedEvent,
{
"id": self._id("evt"),
"created": now,
@@ -335,7 +343,7 @@ def _session_create(self, request: httpx.Request, match: re.Match[str]) -> httpx
"agent": session.agent,
"version": "fake",
},
- }
+ },
)
)
return _json({"data": session.to_dict()})
@@ -376,7 +384,8 @@ def _session_prompt(self, request: httpx.Request, match: re.Match[str]) -> httpx
now = self._now()
message_id = body.get("id") or self._id("msg")
payload = {key: body[key] for key in ("text", "files", "agents", "skills", "metadata") if key in body}
- queued = _model(SessionInboxUser,
+ queued = _model(
+ SessionInboxUser,
{
"id": message_id,
"sessionID": session.id,
@@ -384,20 +393,21 @@ def _session_prompt(self, request: httpx.Request, match: re.Match[str]) -> httpx
"type": "user",
"payload": payload,
"delivery": body.get("delivery") or "queue",
- }
+ },
)
if body.get("resume") is False:
self.inbox[session.id].append(queued)
else:
self.messages[session.id].append(
- _model(SessionMessageUser,
+ _model(
+ SessionMessageUser,
{
"id": message_id,
"type": "user",
"time": {"created": now},
"text": text,
"metadata": body.get("metadata"),
- }
+ },
)
)
self._run(session, text)
diff --git a/tests/custom/conftest.py b/tests/custom/conftest.py
index 7150a96..840d9dc 100644
--- a/tests/custom/conftest.py
+++ b/tests/custom/conftest.py
@@ -1,11 +1,11 @@
from __future__ import annotations
-from collections.abc import AsyncIterator, Iterator
+from collections.abc import Iterator, AsyncIterator
-import pytest
import respx
+import pytest
-from opencode import AsyncOpencode, Opencode
+from opencode import Opencode, AsyncOpencode
BASE_URL = "http://opencode.test"
PASSWORD = "test-password"
diff --git a/tests/custom/fake_service.py b/tests/custom/fake_service.py
index b667e94..7566228 100644
--- a/tests/custom/fake_service.py
+++ b/tests/custom/fake_service.py
@@ -10,14 +10,13 @@
from __future__ import annotations
-import json
import os
-import socketserver
import sys
+import json
import uuid
-from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
+import socketserver
from pathlib import Path
-
+from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
from typing_extensions import override
diff --git a/tests/custom/test_events.py b/tests/custom/test_events.py
index 1e85c89..c82a53e 100644
--- a/tests/custom/test_events.py
+++ b/tests/custom/test_events.py
@@ -2,29 +2,29 @@
from __future__ import annotations
-import collections
import json
-from types import SimpleNamespace
import queue
import threading
-from collections.abc import AsyncIterator, Iterator
+import collections
+from types import SimpleNamespace
+from collections.abc import Iterator, AsyncIterator
import anyio
import httpx
-import pytest
import respx
+import pytest
from opencode import (
- AsyncEventHub,
- AsyncOpencode,
EventHub,
- EventOverflowError,
Opencode,
Reconnect,
+ AsyncEventHub,
+ AsyncOpencode,
UnauthorizedError,
+ EventOverflowError,
session_id_of,
)
-from opencode.types import Event, ServerConnectedEvent, SessionIdleEvent
+from opencode.types import Event, SessionIdleEvent, ServerConnectedEvent
from .conftest import BASE_URL, PASSWORD
@@ -274,7 +274,10 @@ def test_reconnect_gives_up_after_max_attempts(client: Opencode, mock_api: respx
def test_without_reconnect_errors_end_every_subscription(client: Opencode, mock_api: respx.MockRouter) -> None:
mock_api.get("/api/event").mock(side_effect=httpx.ConnectError("refused"))
- with pytest.raises(Exception, match="Connection error"), client.with_options(max_retries=0).events.subscribe() as events:
+ with (
+ pytest.raises(Exception, match="Connection error"),
+ client.with_options(max_retries=0).events.subscribe() as events,
+ ):
next(events)
diff --git a/tests/custom/test_helpers.py b/tests/custom/test_helpers.py
index ac60837..a025597 100644
--- a/tests/custom/test_helpers.py
+++ b/tests/custom/test_helpers.py
@@ -2,23 +2,24 @@
from __future__ import annotations
+from typing_extensions import override
+
import anyio
import httpx
import pytest
-from typing_extensions import override
from opencode import (
+ Opencode,
AsyncOpencode,
- InvalidRequestError,
NotFoundError,
- Opencode,
- ServiceUnavailableError,
+ InvalidRequestError,
SessionNotFoundError,
+ ServiceUnavailableError,
)
from opencode.lib import RunResult
+from opencode.types import SessionIdleEvent, SessionCreatedEvent, ServerConnectedEvent
from opencode.testing import FakeServer
from opencode.types.shared_params import ModelRef as ModelRefParam
-from opencode.types import ModelRef, ServerConnectedEvent, SessionCreatedEvent, SessionIdleEvent
@pytest.fixture(autouse=True)
diff --git a/tests/custom/test_live.py b/tests/custom/test_live.py
index fffe03d..5d8d415 100644
--- a/tests/custom/test_live.py
+++ b/tests/custom/test_live.py
@@ -6,24 +6,24 @@
from __future__ import annotations
import shutil
-from collections.abc import Iterator
from pathlib import Path
+from collections.abc import Iterator
import anyio
import pytest
from opencode import (
- AsyncOpencodeServer,
Opencode,
- OpencodeServer,
RpcCallError,
RpcDefinition,
+ OpencodeServer,
+ AsyncOpencodeServer,
SessionNotFoundError,
rpc_method,
session_id_of,
)
+from opencode.types import ServerConnectedEvent
from opencode.types.shared_params import ModelRef as ModelRefParam
-from opencode.types import ModelRef, ServerConnectedEvent
pytestmark = [
pytest.mark.live,
diff --git a/tests/custom/test_pagination.py b/tests/custom/test_pagination.py
index a7156c6..0a621a0 100644
--- a/tests/custom/test_pagination.py
+++ b/tests/custom/test_pagination.py
@@ -1,9 +1,9 @@
from __future__ import annotations
-import pytest
import respx
+import pytest
-from opencode import AsyncOpencode, Opencode
+from opencode import Opencode, AsyncOpencode
def _page(ids: list[str], next_cursor: str | None) -> dict[str, object]:
diff --git a/tests/custom/test_pty.py b/tests/custom/test_pty.py
index de2fdb5..5218a70 100644
--- a/tests/custom/test_pty.py
+++ b/tests/custom/test_pty.py
@@ -2,23 +2,23 @@
from __future__ import annotations
+import sys
import json
import struct
-import sys
import threading
-from collections.abc import Iterator
-from dataclasses import dataclass, field
from typing import Any
+from dataclasses import field, dataclass
from urllib.parse import parse_qs, urlsplit
+from collections.abc import Iterator
import httpx
-import pytest
import respx
-from websockets.datastructures import Headers
+import pytest
from websockets.http11 import Request, Response
from websockets.sync.server import Server, ServerConnection, serve
+from websockets.datastructures import Headers
-from opencode import AsyncOpencode, Opencode, PtyConnectionError
+from opencode import Opencode, AsyncOpencode, PtyConnectionError
PASSWORD = "test-password"
TICKET = "ticket-1"
diff --git a/tests/custom/test_rpc.py b/tests/custom/test_rpc.py
index d1714e8..7a25fd2 100644
--- a/tests/custom/test_rpc.py
+++ b/tests/custom/test_rpc.py
@@ -5,17 +5,17 @@
import json
from collections.abc import AsyncIterator
-import pytest
import respx
+import pytest
from pydantic import BaseModel
from opencode import (
- AsyncOpencode,
Opencode,
+ RpcError,
RpcCallError,
- RpcCallInternalError,
+ AsyncOpencode,
RpcDefinition,
- RpcError,
+ RpcCallInternalError,
rpc_event,
rpc_method,
)
diff --git a/tests/custom/test_service.py b/tests/custom/test_service.py
index 38aae70..c7d3ab3 100644
--- a/tests/custom/test_service.py
+++ b/tests/custom/test_service.py
@@ -1,17 +1,17 @@
from __future__ import annotations
-import json
import sys
+import json
import time
-from collections.abc import Iterator
-from dataclasses import replace
from pathlib import Path
+from dataclasses import replace
+from collections.abc import Iterator
import httpx
-import pytest
import respx
+import pytest
-from opencode import AsyncOpencode, Endpoint, Opencode, ServiceError, _service as impl, service
+from opencode import Endpoint, Opencode, ServiceError, AsyncOpencode, service, _service as impl
FAKE = str(Path(__file__).with_name("fake_service.py"))
URL = "http://127.0.0.1:4999"
diff --git a/tests/custom/test_service_live.py b/tests/custom/test_service_live.py
index 1f5dfb2..77a369e 100644
--- a/tests/custom/test_service_live.py
+++ b/tests/custom/test_service_live.py
@@ -12,7 +12,7 @@
import pytest
-from opencode import API_VERSION, AsyncOpencode, Opencode, service
+from opencode import API_VERSION, Opencode, AsyncOpencode, service
pytestmark = [
pytest.mark.live,
From 049b757f92d1455a5270faf918fa94a69a29777d Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 18:07:18 +0530
Subject: [PATCH 26/41] Pin the type checkers and pydantic the generated code
is checked with
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
pyproject.toml | 4 +-
uv.lock | 537 +++++++++++++------------------------------------
2 files changed, 147 insertions(+), 394 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index f2f2203..8fc1f7e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -53,7 +53,9 @@ aiohttp = [
[dependency-groups]
dev = [
"dirty-equals>=0.6",
- "mypy>=1.18",
+ "mypy==1.17.0", # the version the generated code is checked with
+ # pydantic 2.13 adds a model_dump parameter the runtime's override does not take yet.
+ "pydantic<2.13",
"pyright==1.1.399", # the version the generated code is checked with
"pytest>=8.4",
"pytest-asyncio>=1",
diff --git a/uv.lock b/uv.lock
index a0591cb..29f2d6f 100644
--- a/uv.lock
+++ b/uv.lock
@@ -231,70 +231,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" },
]
-[[package]]
-name = "ast-serialize"
-version = "0.13.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/21/d5/5dc1ee8a80c3b20a0b63c61b43ba6bc53517faecd1def47bcd752d22268e/ast_serialize-0.13.1.tar.gz", hash = "sha256:401cc84e8888d4d76f2c8d296fa006ed1a5ff993b6aff90dd777c03973efd025", size = 955137, upload-time = "2026-10-17T00:04:17.183Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/f3/a8/fbd136b9f70c59e75f59a8e974c84102976f1201828b4f886480ba956936/ast_serialize-0.13.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:3e129d44b0cffbea24a7f40117ce49e30993c3d119e584d4817ebd6f87246456", size = 897306, upload-time = "2026-10-17T00:02:52.479Z" },
- { url = "https://files.pythonhosted.org/packages/50/3f/2ad51e9823a790e7a70fd781e9595ab3abeace27f3aefcaa1939457efd72/ast_serialize-0.13.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:523d45bab16ddaf1399c04c4fe81bbf180f81e33c2f7bcbe76be56b0f7311ab4", size = 1236082, upload-time = "2026-10-17T00:02:54.82Z" },
- { url = "https://files.pythonhosted.org/packages/c0/5c/8e959db5cc2237f9d4c560a8e2253c98f9746883fb4774651e4cda458901/ast_serialize-0.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:638333fb8ae72c62753f9e794fda20290883ddc65b0d85e26e08f8bb227567ad", size = 1221242, upload-time = "2026-10-17T00:02:56.395Z" },
- { url = "https://files.pythonhosted.org/packages/d6/05/ab1c56c3b7adcac88764bba609502d6b4989e449b2dd60eb478e1d20e369/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc34c670747d85d4232834665e247009413eaebe47c69eaf60a5c439aaecaed9", size = 1282105, upload-time = "2026-10-17T00:02:58.275Z" },
- { url = "https://files.pythonhosted.org/packages/0d/f4/41981fcd2ddffe0760e5326be9dcbd2eda9478a8430a637e5123bd8fcb26/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8c8ebd419811c435812d242fb2a173e57dfede198c9ac9a0a584666ebc9b9331", size = 1287905, upload-time = "2026-10-17T00:03:00.068Z" },
- { url = "https://files.pythonhosted.org/packages/6a/43/b783addc5681153707121991b7b47892f7e9c8ba58a0d03b71a3b023ba47/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8899c038b1c15ae8b98e379057fb3ef398ceb0b21d1737a8156dd5a0923b7e51", size = 1541806, upload-time = "2026-10-17T00:03:01.503Z" },
- { url = "https://files.pythonhosted.org/packages/47/10/969db095038ee15270f65ad20d1129c5e4da605bf7e93b70ae640f1c8a9f/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e14c4de98f965647a74e6eaf37e20207124e45451ccd36e84a6f6ebab45e0372", size = 1309946, upload-time = "2026-10-17T00:03:02.971Z" },
- { url = "https://files.pythonhosted.org/packages/06/dd/7c623d2fb0b868948952d1787e629a43b866f7d4caefd5445e20aefacec4/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3e8a3eff69af640db985360fef35073fb46956dfc3e5c614cf00920d2746dd", size = 1302773, upload-time = "2026-10-17T00:03:04.378Z" },
- { url = "https://files.pythonhosted.org/packages/d4/c2/4d49469110a19a44fc6269f4fd59679bcf84df068f185f89b73bee4da19b/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:75d7750fe98f6f35a96c43aba8d98a2e623e22c778a0c17ab9ede92eb10f5d82", size = 1299579, upload-time = "2026-10-17T00:03:05.88Z" },
- { url = "https://files.pythonhosted.org/packages/9d/9a/2d381f1f262dda15affc8ad643fba1619eb07e7d9f8bd904deffef183006/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e8ddd63e80fc280c2f790157386f27f9b9f6ccb9c4f8e803b863cbfb085769", size = 1355969, upload-time = "2026-10-17T00:03:07.321Z" },
- { url = "https://files.pythonhosted.org/packages/9d/9c/2266794e6c25fa9ce51ce04a9bf4b7e330e3ac9351ba3fa14e487678acdb/ast_serialize-0.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9221e09496fb942901993c1812ee87950cc0ecbb514c7c0f1f5fa6d78f701ed8", size = 1460227, upload-time = "2026-10-17T00:03:08.733Z" },
- { url = "https://files.pythonhosted.org/packages/2b/d6/14deb28e5d8f328f626a0778ea2cf98fe908f2bbcccd804cb6f449ee1782/ast_serialize-0.13.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:7a5c38bdb93669c596f94fb7204dd6ebe4fadb19058ac9a2faf2948c2a175f96", size = 1562691, upload-time = "2026-10-17T00:03:10.25Z" },
- { url = "https://files.pythonhosted.org/packages/f5/19/7a28c0e5ba54f0d3e054f847a2a549b748cb179ab433321a8dc1b39e398d/ast_serialize-0.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:acf4e9f055d75058ab01536da330648746f2f82b7697a85230547f2d5ebe895c", size = 1558669, upload-time = "2026-10-17T00:03:11.799Z" },
- { url = "https://files.pythonhosted.org/packages/18/9a/f9e2690eab58217f1d395a964a78efa7eaf813a1e38be8dfd9a8d45d0840/ast_serialize-0.13.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87427913c325adc811e81e7c9a757b061f47ef1e29114ac479aa0953a2687a98", size = 1665071, upload-time = "2026-10-17T00:03:13.284Z" },
- { url = "https://files.pythonhosted.org/packages/8e/f3/ef5b159a0d7161b404f05e7ec21cf7735ee7edc10c000cd2cbbc7bb6b69a/ast_serialize-0.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:ad88921f95b92bc144d9ad059a0e16f8c1ae69f5239f2d3c6536fd2774f73dfe", size = 1473364, upload-time = "2026-10-17T00:03:14.563Z" },
- { url = "https://files.pythonhosted.org/packages/1d/87/6e8ac4a865124a12cc4f915bf1bcfac3bba426cd5f58d3712a873ee61012/ast_serialize-0.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f8b17e2921e56621bcb46792bd3e0b82ca3b85de78d5936e7b8174ba1758b0aa", size = 1500566, upload-time = "2026-10-17T00:03:15.917Z" },
- { url = "https://files.pythonhosted.org/packages/f5/1e/29155f1621433008013ad5c940a03d41a676be0680c41527040472d37583/ast_serialize-0.13.1-cp314-cp314t-win32.whl", hash = "sha256:2f9a9ce8b0994f757b941f7f7fe384a6666b4162b941ebc5c248081baea18e47", size = 1128674, upload-time = "2026-10-17T00:03:17.481Z" },
- { url = "https://files.pythonhosted.org/packages/bb/64/2506da65143ea138de5822e9173e0b72320a2f70e23bd723f044033175f1/ast_serialize-0.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5d99910e6b9a9b9360bcca2ae99aee36cc912ce058df2a57d1c33099b6cb2593", size = 1163306, upload-time = "2026-10-17T00:03:19.046Z" },
- { url = "https://files.pythonhosted.org/packages/6d/8f/c7cb22638899b0cf15908809c270560e46395e65604bc59e4b623ab9f405/ast_serialize-0.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:a789cbe14d0a0e9ec9b32a456e5abc81ad8799479c3cfd88a29e3442262aebe6", size = 1133775, upload-time = "2026-10-17T00:03:20.377Z" },
- { url = "https://files.pythonhosted.org/packages/2b/e2/b0c140fe50fa39dd249e382f7733af6f816c90021fe7959a0b026f86237f/ast_serialize-0.13.1-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:23d768369b89d551077dc173da6892fad00443338417523b6732f4e8ba4a7ed5", size = 1236160, upload-time = "2026-10-17T00:03:21.945Z" },
- { url = "https://files.pythonhosted.org/packages/23/c4/a8a2ca21e3943555b48221f8fba546877a7ad39efaf6505506524967d7c4/ast_serialize-0.13.1-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:68f8829d114dab6c9db4a8bbb8d0e342958faee1b5e15a29022678df639960f8", size = 1222214, upload-time = "2026-10-17T00:03:23.462Z" },
- { url = "https://files.pythonhosted.org/packages/15/19/2f58f2ba64e4799b39de85c140aef09a03aa6ea53be75dd292a9d9b9d8ae/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e385b17d5bde892a6184003a9aff3518387908b73f8f5ea539313c07c8c0ede", size = 1282979, upload-time = "2026-10-17T00:03:24.695Z" },
- { url = "https://files.pythonhosted.org/packages/b1/af/ca6087fe84210c1ccd43e3182917bb70649fa1e69745a90c1d59ba08fea8/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:02686b281ec9b8e9614f6f0db27e12e4cac515f336c5d05b678bc9eb73ff2af5", size = 1288596, upload-time = "2026-10-17T00:03:26.04Z" },
- { url = "https://files.pythonhosted.org/packages/b0/2c/4ef964c763329a0347125e745ec0451ba4fbaceec9efe2ac729b7cd55f17/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8ab2c2585b2e22e48cd4aff2136fce4eab4ced569b85f150ef9a5a5d6f34d49", size = 1545540, upload-time = "2026-10-17T00:03:27.52Z" },
- { url = "https://files.pythonhosted.org/packages/bb/85/aa961dbb75813cfa94de0ce9a16a9510c226b95f604c405421e727f44bb0/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c652d9f5d52d8b81eff3ff2c95c9dd0f281de45f6ad5d3fa95527550c396454a", size = 1309374, upload-time = "2026-10-17T00:03:29.091Z" },
- { url = "https://files.pythonhosted.org/packages/b4/e6/f2b67c327d9f9533e41791804403ccc2dc457821195a2e51f0b587a90b90/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:121e1d08d58b75c15491cc3003609cbad71ac2b0016043c36d30a6b2032b6605", size = 1303466, upload-time = "2026-10-17T00:03:30.451Z" },
- { url = "https://files.pythonhosted.org/packages/73/1d/33f18b81d1bb1f5d682943ea92fda8130a6593627f12f6201668d40824fd/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:30cf89b6a6844542d116c758725d13de5b97b9410a51ee577715f761270d0c1c", size = 1300576, upload-time = "2026-10-17T00:03:32.102Z" },
- { url = "https://files.pythonhosted.org/packages/25/cb/64914144925f1a4abdd9217e717b122255c38fc5ebe4342f92052f3bacbc/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:732aaae9e498c973fc63fd7f308009b57c4afa6ff59d0b674d894e57d0bca01a", size = 1355960, upload-time = "2026-10-17T00:03:33.676Z" },
- { url = "https://files.pythonhosted.org/packages/ee/80/bd29379fab2bce19adc0bf9177002b11b23dfe8d8fecf409998b458b4624/ast_serialize-0.13.1-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:d9c1a920b2aa0c1e016bed7721689717034ca0dba64ac3a3ff243b003d4e8dd8", size = 1459779, upload-time = "2026-10-17T00:03:35.09Z" },
- { url = "https://files.pythonhosted.org/packages/83/89/012dbe733de05ffdf8e40ca0f9fa5d417cb16ec93a74463c57c001914198/ast_serialize-0.13.1-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:847cad4c22953823f5e76d0b126c6f9da670cfa45b1df41b9324e22189d77611", size = 1563399, upload-time = "2026-10-17T00:03:37.285Z" },
- { url = "https://files.pythonhosted.org/packages/69/37/ee4bcb0358bee38744eb0f18560446572d095fe3bf6b63d6f739d08a839c/ast_serialize-0.13.1-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:aca07de3ca8e3d9f2b716c3c31f4f384fe4bd2d03e09912417cff7a3e08036f7", size = 1558808, upload-time = "2026-10-17T00:03:38.711Z" },
- { url = "https://files.pythonhosted.org/packages/54/1c/ff44c5355251d61ef22934c4430bb31a61de4781d25a992154aac9b8c82a/ast_serialize-0.13.1-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:5db12d843d931b9d1f1519ce66b9cbfe72d8563b75c1204d255322254d1964dd", size = 1666324, upload-time = "2026-10-17T00:03:40.376Z" },
- { url = "https://files.pythonhosted.org/packages/c8/bb/6da4bfc8aa3403e6447607131bf3463acaf954d990c6c8b6787c1902ddfe/ast_serialize-0.13.1-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:fb8021f4dc3296de18737e3b8b34994411da725374d74a0b9eb32a738f2a23d0", size = 1474089, upload-time = "2026-10-17T00:03:41.715Z" },
- { url = "https://files.pythonhosted.org/packages/21/97/897e33bef2eefd880e6350e375cc66bc5bcd00a139be480fd655f5b3f095/ast_serialize-0.13.1-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:0d764913374ba21df8cd618045b371ed104ce55c6276de7c59e554a4f5ba26fd", size = 1501542, upload-time = "2026-10-17T00:03:43.08Z" },
- { url = "https://files.pythonhosted.org/packages/8d/4c/821211ac83870c27e4412381b7c7838336456b40575bf840e4680b2018d9/ast_serialize-0.13.1-cp315-abi3.abi3t-win32.whl", hash = "sha256:2bad302ae2427454ef9dab71bd5c3ad67188860c39d36daee38f72031edb39c4", size = 1126118, upload-time = "2026-10-17T00:03:44.616Z" },
- { url = "https://files.pythonhosted.org/packages/1f/86/2b2d5e6c4fa352874a80618e742ffd32d249dee6c119f4233b51afc6b8f4/ast_serialize-0.13.1-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:032b09d86c785c3984e975378b7861f2a68ac44e22ba7b07b93a536b2524eeb8", size = 1163766, upload-time = "2026-10-17T00:03:45.986Z" },
- { url = "https://files.pythonhosted.org/packages/77/a3/d610cfcdabaa44fd62a54ca1fc57a7dba120643798b089a14914973562de/ast_serialize-0.13.1-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:929eda0c419e622ab49df3a4c45f81c57dcc10874bbad55d0470a4a03324dac3", size = 1134043, upload-time = "2026-10-17T00:03:48.453Z" },
- { url = "https://files.pythonhosted.org/packages/f0/89/0e30f9eddf7d44ccf9d25979030754fd0457af61fa4279acb8c293a04f97/ast_serialize-0.13.1-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:bf53a3ba1cbe45399fc1e4ccd684b8f2a11cca450f89706b79122f478534c6f5", size = 897546, upload-time = "2026-10-17T00:03:50.093Z" },
- { url = "https://files.pythonhosted.org/packages/81/92/d705be88ff452b9ba0088b554a0cbd308fd01879da092b33fd2ad7d1aa60/ast_serialize-0.13.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:4bcf1d5f4eefecaa325a34ecf079b2741189e7d0821523f92cb343a389a67489", size = 1242416, upload-time = "2026-10-17T00:03:51.521Z" },
- { url = "https://files.pythonhosted.org/packages/60/c2/98370845a7f35cb8a25782e78284b48e01bf3128339881d48d613f304f8f/ast_serialize-0.13.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1b158f1c9e3a0d386913924c57995d6df78224868a4d0f215bdd41587c97b34a", size = 1229729, upload-time = "2026-10-17T00:03:52.825Z" },
- { url = "https://files.pythonhosted.org/packages/d8/8b/2d7ffed76addd9d3fcf37fb171400942735c8912264c763561a85407f6a8/ast_serialize-0.13.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a6201b69ce42e919f2fd9a559138d5410b5d8afff7c3a6b1f500b0481ff7d8", size = 1292255, upload-time = "2026-10-17T00:03:54.148Z" },
- { url = "https://files.pythonhosted.org/packages/6b/2a/bb9e0e719d89cc950fadfcfcc9cb4bc7640419c4228c852f031ab8abf440/ast_serialize-0.13.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eb0be64c9b8f1ed26637eef345abe75ea90351c92c85a8aec7838b3af971011", size = 1298501, upload-time = "2026-10-17T00:03:55.559Z" },
- { url = "https://files.pythonhosted.org/packages/7a/32/13ce07dce48226f5bd9cfd8f708ec101a8be05aa430cda4918ce31ce8c82/ast_serialize-0.13.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f2427bddfca208e84de700044fadf9aef2769a70bae740a1bdabb89ee9e1117", size = 1553758, upload-time = "2026-10-17T00:03:56.952Z" },
- { url = "https://files.pythonhosted.org/packages/e2/fb/9b51d186db0c20ca28cbaaefd50f03b573c89327b9a2627820cbce22196b/ast_serialize-0.13.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88df960e67184a71874e5b2ee014f87d1254a0dbc1f903c7bd7b1ab7733ecaef", size = 1318258, upload-time = "2026-10-17T00:03:58.418Z" },
- { url = "https://files.pythonhosted.org/packages/c5/fa/da311559dd3dae9af5825cf21660775a929b970f72e3786af3f2cd667944/ast_serialize-0.13.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f99786e0d62082a5b121b3437f3167562133711df22128de1e9886f57f0b226", size = 1313260, upload-time = "2026-10-17T00:04:00.194Z" },
- { url = "https://files.pythonhosted.org/packages/2b/11/97a4b9e5b4776d72f47fcd9a09a497bbf68ec8c1ae074307a2c51e410d85/ast_serialize-0.13.1-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:a01b515b25a03184eff4acef25b981ae007916379682e908fc8785b342a0bf3e", size = 1309992, upload-time = "2026-10-17T00:04:01.575Z" },
- { url = "https://files.pythonhosted.org/packages/2e/c4/62f85121a80cd451ef04f550cf0f7ea0ad9ff46cc4e9a5b9ae439b6527b1/ast_serialize-0.13.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:29422d7bcd9e7f39411571c36cec45ce8a7a9b3ad4b2e7aa64497c15a9e397f6", size = 1362715, upload-time = "2026-10-17T00:04:02.881Z" },
- { url = "https://files.pythonhosted.org/packages/c1/fa/c5f4a545f72a7681e3295bab6e3600ee0862ec4391bed961fe9f735b67e0/ast_serialize-0.13.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6ffb0f548a7dc126067fdfb7b2325ab5dfcdeecc29faf5e0f0461c96d6ebfa3d", size = 1469931, upload-time = "2026-10-17T00:04:04.181Z" },
- { url = "https://files.pythonhosted.org/packages/38/a9/65aed4f8fa7a001a7cfdc03e68e844517ab041ba6fb614b7d78a1d6ac056/ast_serialize-0.13.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:2876444a5f87a7c76f371af3c07c2fda563a08bc8af52568df812e8aba4bf568", size = 1570945, upload-time = "2026-10-17T00:04:05.778Z" },
- { url = "https://files.pythonhosted.org/packages/fb/8c/44b2da991cede79bfbf52089f33a5f0168945466b3d9cd38b5dcf08e7130/ast_serialize-0.13.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:885a62ec6481ce58ab24c6357360d7a82023b62dd7e5e7ceccf59e9662a3272b", size = 1569978, upload-time = "2026-10-17T00:04:07.347Z" },
- { url = "https://files.pythonhosted.org/packages/46/e4/109b2a5beca700237767d5add920c2f8e1ba9bd5799b0faf143d0e9cf4ef/ast_serialize-0.13.1-cp39-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:2015fd0165e463aac3b1aadc6a1fa1e5820c14ca950821f4fe02f8a3f6a388d6", size = 1676082, upload-time = "2026-10-17T00:04:08.7Z" },
- { url = "https://files.pythonhosted.org/packages/73/e0/7df70f6dbb11b5762da5416318f08316a1c51348e0d69ec79ca5f98f836a/ast_serialize-0.13.1-cp39-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:eb41e1906502ac80d24ff343f45008552a3f5b5158607959fe6a07dbfc6eb037", size = 1483207, upload-time = "2026-10-17T00:04:10.162Z" },
- { url = "https://files.pythonhosted.org/packages/1f/bc/523aa1c08bd69b5654621beb3dc1a9035715a4a63762fb8d219d6fb3eb81/ast_serialize-0.13.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ca9acc67f796aa2554fa3037dccc022a4b08b38b2ad153edfac9c9bb7cb1f3c6", size = 1512140, upload-time = "2026-10-17T00:04:11.589Z" },
- { url = "https://files.pythonhosted.org/packages/66/83/6607060659998f00afdea0d8375c1e6e2b7d515ab905090d9a5a02f48851/ast_serialize-0.13.1-cp39-abi3-win32.whl", hash = "sha256:4f98da562e5901d69c7dd30885594c5438f859edf354e9ac7517d188fb026c3a", size = 1131889, upload-time = "2026-10-17T00:04:12.953Z" },
- { url = "https://files.pythonhosted.org/packages/9f/6f/bafd6490fe7b355627fabe95f4417e3d02da93102c8552550eb9a0774e1e/ast_serialize-0.13.1-cp39-abi3-win_amd64.whl", hash = "sha256:ddf7cbc9c8abf70102c4a088efe154bb86bfb825b0a10e556d2b15972cf99bd7", size = 1171153, upload-time = "2026-10-17T00:04:14.362Z" },
- { url = "https://files.pythonhosted.org/packages/11/25/4235f4b375a67a37ac595bf478a5407a744eaf7b64113c1c3a5432b8d954/ast_serialize-0.13.1-cp39-abi3-win_arm64.whl", hash = "sha256:48f11d96c9ec71119612f8e79a8c55c12f2b136716d68347433c561d27a25f43", size = 1139035, upload-time = "2026-10-17T00:04:15.767Z" },
-]
-
[[package]]
name = "async-timeout"
version = "5.0.1"
@@ -1080,136 +1016,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
]
-[[package]]
-name = "librt"
-version = "0.16.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/04/f5/9dc696772d241814bacac7880bac32f2930b5a6ebc1f85317b83161a011c/librt-0.16.0.tar.gz", hash = "sha256:ac38d6d8d66bf3d744148dbbc0b8e193e195a51e364ed55e224631f5721891fc", size = 219839, upload-time = "2026-09-29T00:55:32.891Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/ec/77/900294b3c9a81415323d81b3f3f271a7f5d7ed9d01e824fc38ffa49470b6/librt-0.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:96f576f2711f8519152ec76d0e599243555c1f07679fa73606ca8c8c868c0be6", size = 148668, upload-time = "2026-09-29T00:44:20.282Z" },
- { url = "https://files.pythonhosted.org/packages/55/3b/c301e8cc47758e4518b93e926a0111080155cc79fa03651d202ac2025065/librt-0.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2bec3818c7da7c96ceae0ef5915a3d16c52dd08f3ea913bf1fe8568c447c7978", size = 153562, upload-time = "2026-09-29T00:44:21.672Z" },
- { url = "https://files.pythonhosted.org/packages/62/b1/4d5b98114a58bf17e3832cd33400e40f2582f68c2fb7ce25b4468cb1afd3/librt-0.16.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:64c79520414a3fdfc6aabd7593e6169afa14d5f8d9908d4b498db068868b08dd", size = 494337, upload-time = "2026-09-29T00:44:23.096Z" },
- { url = "https://files.pythonhosted.org/packages/88/36/14223c9b0dc38982eb4f399732c8cb27b19c7e027ae0faef5ca51d08d19f/librt-0.16.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f81b5b19ce748ef68d4746656b7929762eb2fe99269b266e4be07e2ee4de7144", size = 485440, upload-time = "2026-09-29T00:44:24.555Z" },
- { url = "https://files.pythonhosted.org/packages/ec/9e/d7155e6800d0c33e297e529b217649f7361d706491ebf9d2fc0fc627309e/librt-0.16.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c71d1b76210a36729fedfc5115069b50a3d8619054745f8758fe5d6f19e86671", size = 498363, upload-time = "2026-09-29T00:44:26.03Z" },
- { url = "https://files.pythonhosted.org/packages/d3/ce/d5f1fb9f913ac0979d6fc5db9fb9c0bf28cc3470988528957922f15ed40c/librt-0.16.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:473eebc7866bb0a0c8849a292b5e7157c1aba5d14d0f0f610c52158d6d964262", size = 515453, upload-time = "2026-09-29T00:44:27.566Z" },
- { url = "https://files.pythonhosted.org/packages/b4/d4/ca0fca1a27c26080d1a25b2b31f271e532078a5719878c3daca0c3d4c539/librt-0.16.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4eb1313a19847089ee81e88742abedf285c60538816640b99742d8534b81d26a", size = 509537, upload-time = "2026-09-29T00:44:29.14Z" },
- { url = "https://files.pythonhosted.org/packages/ee/c0/26807bf2221a53a2c66f2e23a5c4d57f1c0eaa4afed7cb81d841cf3d1903/librt-0.16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ebefd60b42e2a82b32d136bb5f7c94eadfcd29f772b547df6f3291d1ed855a1c", size = 532541, upload-time = "2026-09-29T00:44:30.745Z" },
- { url = "https://files.pythonhosted.org/packages/0e/50/c329d509907e1bc061abeec3796f94d255d43ba25d28c3854d2138ed5ab2/librt-0.16.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6c5da27e8056439f927ea896735da60e616c477a8293feaa3233d4e7781a6726", size = 537059, upload-time = "2026-09-29T00:44:32.102Z" },
- { url = "https://files.pythonhosted.org/packages/84/d6/4571b9c9e65d98e5503734166dce2fd3e216f526dc3c59a3773faa8a60e3/librt-0.16.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:67e718c7a43f8db325abbbf1404e2d535f12f8f7a1a82259568385cc5274b82a", size = 536614, upload-time = "2026-09-29T00:44:33.653Z" },
- { url = "https://files.pythonhosted.org/packages/c8/d3/a2ed8a292e4ab7889792a9c3f4ad5d1caf6557afce7f6b452e77f8ed20b7/librt-0.16.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:314e703f0c19320dc8094e7a784b9cf29e1b67402515abf580069a6363c0b4f1", size = 517199, upload-time = "2026-09-29T00:44:35.115Z" },
- { url = "https://files.pythonhosted.org/packages/9e/24/276e5218b4b7adb6258a42b7403cb1b7cef25616238d0c2152288a9d2f53/librt-0.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:943c6bbecbdf7fa575a4f2952fcfd848c88ef95507c3fca411e89d4ac3ff8143", size = 558746, upload-time = "2026-09-29T00:44:36.656Z" },
- { url = "https://files.pythonhosted.org/packages/7c/5c/9156df81a1726e1efc9567379def2b8777a6d56f464d2f1b760bf951b8af/librt-0.16.0-cp310-cp310-win32.whl", hash = "sha256:7cc365f006891afb006b52d5ee5ee74c09306ffa20e2f8705a32a4450af2f3ba", size = 104424, upload-time = "2026-09-29T00:44:38.123Z" },
- { url = "https://files.pythonhosted.org/packages/0b/62/4fd172cb3f49ab2cf5ef17de519a93cfb935202c1270ba9d44c6a64178c3/librt-0.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:0314058469f4d2fd279ce7c62ac274ac82c3918ef7db62ef0697c4c359370155", size = 124267, upload-time = "2026-09-29T00:44:39.455Z" },
- { url = "https://files.pythonhosted.org/packages/ba/0e/b04fd2d76b6a78a02286938251b2c119fb36ddece92c76cb4886ef7d44b8/librt-0.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fe4372c52d4849096c6cc1cda2817d293ec51440c890474ed59ef38d46556f18", size = 148058, upload-time = "2026-09-29T00:44:40.903Z" },
- { url = "https://files.pythonhosted.org/packages/a3/fc/e1ed24aadb32d38603c0c07f579d739eb4d90e64e90d525b024e4a999b4d/librt-0.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c72c5295a84bd249526da9bdca38f2e176d15c31c13bb0063c5053f4ca023421", size = 153073, upload-time = "2026-09-29T00:44:42.165Z" },
- { url = "https://files.pythonhosted.org/packages/be/38/133ab655409420e0c34a9a9836a7f4e5ae75fba758d7fe177099130d58ba/librt-0.16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be56ba9c884143495b517f23fe794ae367d58cd89ea0fdd6d437e3c024a87f9f", size = 493052, upload-time = "2026-09-29T00:44:44.043Z" },
- { url = "https://files.pythonhosted.org/packages/94/42/aaa663bc9421f5bf7bbbe9d3316d7219749698ec7688ac3b519770a89c04/librt-0.16.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:ef46c1a29ffb8c72e882e22618ec618778eacd0578fb22c6e7cf9c11d15f357b", size = 485506, upload-time = "2026-09-29T00:44:45.479Z" },
- { url = "https://files.pythonhosted.org/packages/7b/59/83a6eb5087ba8b45b083859e2085256963e06d4c2531944cdd962670bfb5/librt-0.16.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d3c94211ee0c4f8d649ec06b7c115c0ec4eadb873a0e3154ca15cef3f814b071", size = 498708, upload-time = "2026-09-29T00:44:46.916Z" },
- { url = "https://files.pythonhosted.org/packages/a0/e4/15a8d62b2bc12935080ec7277e2e0b9b233047781eb784a51d3e25af669a/librt-0.16.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94aed6a8308818b91677957d1bd03188869cd7aeb23c5dba7912a6c0402f7602", size = 515981, upload-time = "2026-09-29T00:44:48.316Z" },
- { url = "https://files.pythonhosted.org/packages/46/4f/287cc282ac5dd815204c0779e79401bb0a568c3a1335fb319bf0838fb566/librt-0.16.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:349c0bcb87ebd07481b6ff781e25cdc699723dbe2212e57dabb27f7a13b7b87d", size = 508670, upload-time = "2026-09-29T00:44:49.726Z" },
- { url = "https://files.pythonhosted.org/packages/bf/5b/ab7292add898ade0d2b5b75d32a923aae315861671aa5c39f5491309f7ae/librt-0.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:001bfd59a7d45b17e3e75f2a8c6405280b35e7b84471792778e718c4f368950e", size = 530393, upload-time = "2026-09-29T00:44:51.356Z" },
- { url = "https://files.pythonhosted.org/packages/7a/9e/8e6bcf027d4bcbc16a4e864b3d158a1d3e5f55ffa53af0238020cad89271/librt-0.16.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:28e038895b998d7a0c7798922ce8a1dc157675df5cf1c9ef0aca809ed804b7a1", size = 534302, upload-time = "2026-09-29T00:44:52.893Z" },
- { url = "https://files.pythonhosted.org/packages/96/a4/8c5ab4f3b3a8e905ff8305d76976a2e666f52fe049edd398aed2402beb36/librt-0.16.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0dbe4096a7ecc00fa835d24510ad8545a4efef738dac96e0e63516783ccde905", size = 536150, upload-time = "2026-09-29T00:44:54.421Z" },
- { url = "https://files.pythonhosted.org/packages/ea/5e/7c4bc267e3cd4b8bd288de9f62d61fff203829c9cc649ea2aa12e9218cf0/librt-0.16.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5cd5b092441053364af968ea12084692cb9d4a22f3ce9524e377880bf028761e", size = 514316, upload-time = "2026-09-29T00:44:55.998Z" },
- { url = "https://files.pythonhosted.org/packages/e0/75/c8bc6b50f43c73819334233f5a3259c81216742cef56d2140c3dd377c3f0/librt-0.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ddeb3c9dedb461bb457c6c7d9aa7fbf35329da313d1a7543d00c8d0f3473c96", size = 557731, upload-time = "2026-09-29T00:44:57.581Z" },
- { url = "https://files.pythonhosted.org/packages/93/fb/e241f91bc8de6c7ac1c6a20ea6dd381bc52312b44d711ffcd97140db1e57/librt-0.16.0-cp311-cp311-win32.whl", hash = "sha256:e05108e0849966f53a8d2d3112a7af881d0efaa479bc735bba91108f9f2350a7", size = 104922, upload-time = "2026-09-29T00:44:59.078Z" },
- { url = "https://files.pythonhosted.org/packages/d1/38/c892383d2bb1a6a80b2ec8ede84970a265a792c56614a06282bf3008517c/librt-0.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:5f49cff01bd608ef7d97104cb035c75455e79c2d70bf4a506cf773338ac1860d", size = 125093, upload-time = "2026-09-29T00:49:01.621Z" },
- { url = "https://files.pythonhosted.org/packages/e4/56/8bbde1ed0b21480163617648f638fdaec9374cf5e9c846ebcee34ec02a42/librt-0.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:1d28ae980ae2218f9c5b95d191e947296f918c9bf0b400d467a9430275bbe678", size = 115910, upload-time = "2026-09-29T00:52:40.131Z" },
- { url = "https://files.pythonhosted.org/packages/ad/76/bbdaeb87b7c47b5c7343e90222b9bfa4e4a8a83f647e02933ad0129225b1/librt-0.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe52bf4641069e7978a14253b036cb9002def1926317e710f2e249f8a8c47742", size = 151028, upload-time = "2026-09-29T00:52:41.508Z" },
- { url = "https://files.pythonhosted.org/packages/fd/0c/ab8ed3dab0085931aec4a792c7eaac8dc6c5ff4691fda3a5360d9d8a9cd2/librt-0.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5bcc2c4726ced915b00de0c9856a4eeabfb3fddb93e10e0b8f735b7709358b6d", size = 155283, upload-time = "2026-09-29T00:52:42.873Z" },
- { url = "https://files.pythonhosted.org/packages/eb/36/494e79d460c80c1f030661e8287c9eca5e1ad652dc2b2180b6cd42abce0a/librt-0.16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff7baa55f8e7c69851419e50a666015d02a74198716fd45c0125a2112e0a389f", size = 503185, upload-time = "2026-09-29T00:52:44.638Z" },
- { url = "https://files.pythonhosted.org/packages/9b/34/a8464038dd9db6e4381fa2b6eb73dc9a50888d77102c4c139304ac35cddc/librt-0.16.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:b95d5d92ab83d39e760a52091bb1baba664f3a2351e39b1e16801e5747c2f0e9", size = 496738, upload-time = "2026-09-29T00:52:46.441Z" },
- { url = "https://files.pythonhosted.org/packages/6a/53/e0e5e334ef0c6ed27039d323819368b9ef6712be87d55ee2bf9799398afd/librt-0.16.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b6d085d70bce51d43c5c7c36d63490770180d8779e71c49305c87b4213918de7", size = 513818, upload-time = "2026-09-29T00:52:48.068Z" },
- { url = "https://files.pythonhosted.org/packages/ad/f7/7ce72cbf19d0addd05090b339152fd0548a02562c2866a603e6e3b3da2df/librt-0.16.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36e53948e99bbe3ffea257124cfcae1cfb01831555c9a9c903c9f9a72db7fd07", size = 531952, upload-time = "2026-09-29T00:52:49.816Z" },
- { url = "https://files.pythonhosted.org/packages/83/22/0b1bcb6a8e723c8b4fd60dfc8ae8ec6461c54073fbc8685efeb8d900d407/librt-0.16.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:54d11f726aae9df5a6ffbbf0a03a52449bbac84a53ef03669cb41cdfd4ae41bf", size = 524465, upload-time = "2026-09-29T00:52:51.354Z" },
- { url = "https://files.pythonhosted.org/packages/64/2e/e9c23b8b9df1813da1be205deca9606beb7ddd033972246cd426d05374a0/librt-0.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4323193ac0cd025f85af531df8ba91bf24d1973b401697347a6282e8fd3fcf5e", size = 543191, upload-time = "2026-09-29T00:52:53.284Z" },
- { url = "https://files.pythonhosted.org/packages/bc/e5/6a8b21b342c03ed7e230fa3afbfd2edc58e6e87ef1f0d11fa2b9a748c252/librt-0.16.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e42f8e098b9c5396fefa05fb1cc7e33b0e08fc51da106b5de4a45fd22aac6743", size = 546979, upload-time = "2026-09-29T00:52:54.932Z" },
- { url = "https://files.pythonhosted.org/packages/84/9e/b5129023eced1be01e01c22757f53be551d463b1bb7264f787927404c1d6/librt-0.16.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:39ec1d5a14e37baf1450a6cabf03fe552340808bf1ad9d71824ab90117716459", size = 555317, upload-time = "2026-09-29T00:52:56.869Z" },
- { url = "https://files.pythonhosted.org/packages/71/89/28bba5938c725fe91f06bf93f7fa6c6b150229df53a87b454b0d5c2a796e/librt-0.16.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d1aabe3925cbb4a08d15b7b20ba4011b53019da0c4173a25155139b7b1baed65", size = 535846, upload-time = "2026-09-29T00:52:58.475Z" },
- { url = "https://files.pythonhosted.org/packages/22/92/63773026614f888c5d4e370e395ce42ca604b89f70b3acdfedbf94851b80/librt-0.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:300c3ffdc459f4a779a8411ecb188e3ac0b1ff3a3a7b099642555dedae06c69b", size = 573700, upload-time = "2026-09-29T00:53:00.127Z" },
- { url = "https://files.pythonhosted.org/packages/66/8f/347d4677eefb57cd9f8e01d95a1dcee9a91b4e44e664173c32ff6f3752e5/librt-0.16.0-cp312-cp312-win32.whl", hash = "sha256:c17194318e4c0c0348b36f36c2ec7534436fe0a4c15582403162a4f08c80797a", size = 106158, upload-time = "2026-09-29T00:53:02.031Z" },
- { url = "https://files.pythonhosted.org/packages/f0/2c/5193dc81127cd5ddfad031391b046bf32dda219b38463ab872407ca30646/librt-0.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:25a58a19ea8d83b68209f04912df765e9260635ef77646542ed4b4abe6bc7940", size = 126176, upload-time = "2026-09-29T00:53:03.445Z" },
- { url = "https://files.pythonhosted.org/packages/ff/3d/9668a400c8dd81d162eba38b33fa49fa6205f1a64493570a13fbb815c3ee/librt-0.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7be7cf555bc30ec12622e9447299cc4a9b8ff307548b634794353db0c2065dc", size = 116572, upload-time = "2026-09-29T00:53:04.815Z" },
- { url = "https://files.pythonhosted.org/packages/46/cd/ae5e0e9dba45d1399aa04a5395bcc0bead40d9fa06dc903634a7b4d7473d/librt-0.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c5e6144e68b577f157519f2ba88ca20e3ed61c29b00e5cdfa76cd2d45acf059a", size = 151036, upload-time = "2026-09-29T00:53:06.284Z" },
- { url = "https://files.pythonhosted.org/packages/41/5a/48a16e323c5f9447a94cce7b59babf60fa04e62c3365ecf060c77ed8b320/librt-0.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:33f41443a1f4e1f099331b3d8120e409fbff84b9760bc1cc9ea496f37ddaa5cc", size = 155211, upload-time = "2026-09-29T00:53:07.72Z" },
- { url = "https://files.pythonhosted.org/packages/3f/29/0f59299eb4251a409b2e690ad4b7d9f8a676db829d7817ec961f32b44f7e/librt-0.16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e510b7770bee609617a3374a96548eb114cae048023e3f049ee449e7ff2db32", size = 502587, upload-time = "2026-09-29T00:53:09.191Z" },
- { url = "https://files.pythonhosted.org/packages/de/ba/d6fb4ef8d1537c396079d72289f16be7cd35a366e5065c51253fea2760b6/librt-0.16.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:efc49c462d4516b8a58b00b490078fa64689fd1fe66970cc190131d7afb8027e", size = 496146, upload-time = "2026-09-29T00:53:11.016Z" },
- { url = "https://files.pythonhosted.org/packages/52/fc/8c50dd4d7cc97c0ee8f252c8a3104980f234391cf1519b554e8b9de08b60/librt-0.16.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:92caf82ebef5e12d21c72242b70d1e92536f1711cf2a727a4c276de4b4469087", size = 513336, upload-time = "2026-09-29T00:53:12.655Z" },
- { url = "https://files.pythonhosted.org/packages/a9/59/16c409c56f708eda2db9a0553662845d45e3871c77d70a240dae3f3bdc56/librt-0.16.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17bac7f7a16b328fff77e440287693eb017abde913595b5827ebccbc21ecd8a6", size = 531689, upload-time = "2026-09-29T00:53:14.39Z" },
- { url = "https://files.pythonhosted.org/packages/88/82/d34772a6c29d1446dcca6e64d74062efd508523ab351aa16625a4689d5cc/librt-0.16.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5b976054553670829985ed767feb78fb6bcede0175327c4844dd5c281c1be659", size = 524537, upload-time = "2026-09-29T00:53:16.064Z" },
- { url = "https://files.pythonhosted.org/packages/77/8f/24c5631313746131ccee53bc91fdc8374f9cf25e0082a1fee9c93bb98acc/librt-0.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0058f9d68721094105917254c72ac0569117bb7b13b9769cf45d26d89f9d21cd", size = 543231, upload-time = "2026-09-29T00:53:17.939Z" },
- { url = "https://files.pythonhosted.org/packages/f2/cb/5f8e0d41dbd8b499c2265e939c31acc9ba59845565bf99539ad1c06aebcf/librt-0.16.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b7beaf3f4487b7d8adef1f158b49067cb4d5a19fa7a3bf31a4e7a820e435c5", size = 546499, upload-time = "2026-09-29T00:53:19.666Z" },
- { url = "https://files.pythonhosted.org/packages/be/61/063052de441d1385f59cea4223f184bf9e5d125de1ae3239b490aa1e486e/librt-0.16.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:468df902df016a06eb0e40b0747dc8d14e47d7a38b18b63b1fb167d85cb94d63", size = 555261, upload-time = "2026-09-29T00:53:21.292Z" },
- { url = "https://files.pythonhosted.org/packages/14/11/a2ada0529372268d6401afa9d457a095b68cd7753532b6f7f33049a19b43/librt-0.16.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:aea7b1f2b125dad5de85f049136651bff256c883c65e6b9209b2da0a1ac3cdef", size = 536029, upload-time = "2026-09-29T00:53:23.07Z" },
- { url = "https://files.pythonhosted.org/packages/23/9d/5bb6d38853382986dca702fc7e06c8256d30f5fa0676d882773b744610dc/librt-0.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a8afb6557920860b7a3a596eb804cf37e09e7cf8a803db2478c202acc72d8c2e", size = 573748, upload-time = "2026-09-29T00:53:24.696Z" },
- { url = "https://files.pythonhosted.org/packages/99/f6/0025cde35ff7f607684dc775a2b2d732cce2561cec050a6ee1fb2e1fc6fe/librt-0.16.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:77c7a2b4fe2c1369e0d5aa1cade26740a7b14be32fbc9a5535d617d20065c39d", size = 79861, upload-time = "2026-09-29T00:53:26.089Z" },
- { url = "https://files.pythonhosted.org/packages/93/93/303b8592909bd583f83f02818ffbea3f7647ca1e22b1cd5465d04b8145fe/librt-0.16.0-cp313-cp313-win32.whl", hash = "sha256:02d89c813d5ff74b17df72d3a34819d132cd168e56b81bf755b809bd9e46b8c4", size = 106236, upload-time = "2026-09-29T00:53:27.43Z" },
- { url = "https://files.pythonhosted.org/packages/cf/24/80bbb463c60ed18e29cb26aba386ddb76609580e4e7160710e550587018c/librt-0.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:14ed6ebe3e4f85f326d7920011ad30ff49ed9334e62cf88caef9ba973d9e3a92", size = 126180, upload-time = "2026-09-29T00:53:28.7Z" },
- { url = "https://files.pythonhosted.org/packages/43/80/b1a6fbdd7da825cdd55c71aa81eb6cfa82c513c360774152eacb50b4a771/librt-0.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:83d4041a3d9b2fd053a8a4e1f22878b3e5833e2712956382d5c048d791454e91", size = 116557, upload-time = "2026-09-29T00:53:30.012Z" },
- { url = "https://files.pythonhosted.org/packages/1e/93/9e0cf7da129a93c3dc7f45bc3cd4a660f2aaa995aa8a6f95c2583ef41239/librt-0.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:931a0bb0fcac88f263e269e46eb30ba8e21402cd3c62ca40cb97034c0693fab1", size = 149844, upload-time = "2026-09-29T00:53:31.391Z" },
- { url = "https://files.pythonhosted.org/packages/8f/26/8a90d2a8f2b2e471bb486b7aec117b8ae622715ed6c39853aec48ea20073/librt-0.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1bc17e54e5305f8d40b7ca203671ff5a9e59c1d0f8ea0f625dcca53a3984de11", size = 154111, upload-time = "2026-09-29T00:53:32.718Z" },
- { url = "https://files.pythonhosted.org/packages/35/ce/67abb46258da4d3e42ff5b141db6f38c59357bef84c7979f183b22f924f1/librt-0.16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:877698bf6bca5721d8be345f2fe09778e40ecadea8b58c73075f2b1a53666bf2", size = 494234, upload-time = "2026-09-29T00:53:34.466Z" },
- { url = "https://files.pythonhosted.org/packages/12/f9/ea7162414a16f8f1bbd3b493ad6d22b926c5471916e078350bdbca8c4e5f/librt-0.16.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5981c011b306781ce561e18e14230a14524a3d8109b97553666c942c18f31a96", size = 491173, upload-time = "2026-09-29T00:53:36.124Z" },
- { url = "https://files.pythonhosted.org/packages/b1/09/9b3e869060dd33f9989b80ba4fea306f6db8cecefcdfe6d7346ef0603f65/librt-0.16.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:afced3dfc17cd805ecf7a3d77996a71cf5f2c75aa66eb0c21a9930f4fc992f86", size = 505547, upload-time = "2026-09-29T00:53:37.686Z" },
- { url = "https://files.pythonhosted.org/packages/50/07/79007d2165f649ea93e08c0962d1d62c70af9cde77965255095bf9d96f9a/librt-0.16.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ca8052401c55d7511dda6760719fda7618067e83535d7d0010096d216c34b667", size = 523057, upload-time = "2026-09-29T00:53:39.301Z" },
- { url = "https://files.pythonhosted.org/packages/61/0c/8fbaff66d0ba376d8864653f5acce1569bae27e89648671f26f7eca67ab8/librt-0.16.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1e511762a074005bb0aa569166779834e75e438370226930d0ce1866d4b6a33b", size = 515158, upload-time = "2026-09-29T00:53:41.012Z" },
- { url = "https://files.pythonhosted.org/packages/df/2e/23ff0dece76f07a4124413a57682efa0bbeb5765ac0122bc0955513f82ca/librt-0.16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f1e8591bd8a5a628cd7f07954c6a1592359a878bf032957a8e9057a41d644311", size = 534183, upload-time = "2026-09-29T00:53:42.451Z" },
- { url = "https://files.pythonhosted.org/packages/26/c4/e11dea21d9a29486eba78887380374189d472734fa32cc50cb37ca44d3d0/librt-0.16.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4aaefb4ba6c07e1aeebb2795c8958148f1d6f9af3b555b53d23d766edb6d67a", size = 540663, upload-time = "2026-09-29T00:53:44.272Z" },
- { url = "https://files.pythonhosted.org/packages/44/75/e873ae158a8b7f5359be33e7fd6c1fbe02d9a78a3e89a77de6b0e837f476/librt-0.16.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:d92db7a0f6aee44f1baee94750457e8d2d1c6ccea41842de6268d34e8dc7eddd", size = 545908, upload-time = "2026-09-29T00:53:45.812Z" },
- { url = "https://files.pythonhosted.org/packages/ba/36/8939d3f6a93e11bd9592e6fe28d2b44f1c2dc4bed6e22e72359a91e18ffb/librt-0.16.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:378dfaffb38e59c24a87cde5713cd865d51ff7383fa12947f3907f306ea1ca55", size = 523302, upload-time = "2026-09-29T00:53:47.596Z" },
- { url = "https://files.pythonhosted.org/packages/71/14/35309f44a077f0f42ade0e2e7cd88c0cea760c661af205c01ea90d3c0e1f/librt-0.16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3e0c39bdc85370422e8b637be76eb1fd07d30967551b03e62267dd156f553152", size = 565911, upload-time = "2026-09-29T00:53:49.278Z" },
- { url = "https://files.pythonhosted.org/packages/78/0c/df6255b94967f3159ebc46f08d8e783c12da6ee269ddb74b2efed63c640c/librt-0.16.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:1b384b90ab79a7bc30b566895809a636e0666f21f3cf12b54823d025b7e83839", size = 79119, upload-time = "2026-09-29T00:53:50.938Z" },
- { url = "https://files.pythonhosted.org/packages/6b/44/d30d5a5461378c9d33f36736c6791c3b4c4ba4b1ffe0da7350aedcb2c9a0/librt-0.16.0-cp314-cp314-win32.whl", hash = "sha256:52327da75a94012e7f932f913d20d3876bed3c102be00e6c3e8600ff7bdd58a7", size = 100140, upload-time = "2026-09-29T00:53:52.205Z" },
- { url = "https://files.pythonhosted.org/packages/c2/98/769712f356a1e897df3581bb0c3100375d054a000de26099360ea65b5111/librt-0.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:3f0b8114c44b2ac06ff5dacd08e07e8e807ff4f46083f2a1602685122559be41", size = 120464, upload-time = "2026-09-29T00:53:53.495Z" },
- { url = "https://files.pythonhosted.org/packages/bf/d3/ae2abccc8bdc8b063c1613a77686e17b74e9d3d60cfe6f12c63fa2821b9f/librt-0.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:8caf96a4ef8fb27d0ac0d1ad8337d26a240acd4a02fe4345d0a8f264753e8f99", size = 110889, upload-time = "2026-09-29T00:53:54.817Z" },
- { url = "https://files.pythonhosted.org/packages/e4/26/0737d4be058dd6376eade7dd8b380d4b869b6394cee929393a5a431c45bb/librt-0.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:953107e2f68d0f3512c48f898b0dbf0ce5cc52bba0f318d847c985dc555ee4cc", size = 159838, upload-time = "2026-09-29T00:53:56.22Z" },
- { url = "https://files.pythonhosted.org/packages/01/96/9bc96531d7c620e9949af904470f02e3fa8f35129ab8e8f281c51eaa3788/librt-0.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad37d5b9abd49c9a655dcda7ea52a8a752884062ef1ee71ae17c2f2a0f81fe6a", size = 162018, upload-time = "2026-09-29T00:53:57.532Z" },
- { url = "https://files.pythonhosted.org/packages/0e/fa/b0289dcb186eb3f97221ba00da5f4bd3ba7fa5e99752d48aa8d336615334/librt-0.16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c4aa329c17bd1aaea4f6e89335d8ccd494b3a5830b6654462273e50e11023f0", size = 703983, upload-time = "2026-09-29T00:53:59.024Z" },
- { url = "https://files.pythonhosted.org/packages/d9/ab/05ebbbde7530fc5eeb58fbd1581522a9f64f132fa703c4c595eed6e14760/librt-0.16.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0ead24d2562a49473dddd9efef8581f020007eb0054389c3ee3ffad38b1ca4c9", size = 683847, upload-time = "2026-09-29T00:54:00.671Z" },
- { url = "https://files.pythonhosted.org/packages/a3/75/f52aeecd4dbadbddf80725ba7de126d8bd5d0eb66247eae17a81ed90dd4d/librt-0.16.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02118f56a9c36ddd07dfd9b919d9ecc117ba20a90987d56aa4c429fa34509188", size = 698045, upload-time = "2026-09-29T00:54:02.247Z" },
- { url = "https://files.pythonhosted.org/packages/e6/55/fa277a835cd6eb42380591ceb85f48c5b4d2b2d7e2cb9869e1e17d24d237/librt-0.16.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e29522c62e28595ff7e324c6834ade51127707f0e255b18d1c1cf03d39c1048", size = 724640, upload-time = "2026-09-29T00:54:04.078Z" },
- { url = "https://files.pythonhosted.org/packages/25/e4/2cf64354f3fde8ebd591b3b48f96510bee24ac5f7d1e567adbbe210abdbd/librt-0.16.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ff4b2367926b69c6215635902cccb04048e73094e9862900d27cb2c6bbff143", size = 731748, upload-time = "2026-09-29T00:54:05.741Z" },
- { url = "https://files.pythonhosted.org/packages/f8/c9/c180af3e94e01aa529fa93d7733fec2abc47f222345400cf21d5481d5f8a/librt-0.16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c6f1b27bf1632a7e016af9f145f82be95e1edd7721a646505c21059257cb5a04", size = 754960, upload-time = "2026-09-29T00:54:07.38Z" },
- { url = "https://files.pythonhosted.org/packages/95/d6/01073aa78c58f356b10d9c57b3fe9abb143df338142bcd316df417b89db0/librt-0.16.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5696d7f52e7b37217cb3a8f92c744fe835942602fdd4c1a8bc4741d3bfdce15e", size = 747000, upload-time = "2026-09-29T00:54:09.06Z" },
- { url = "https://files.pythonhosted.org/packages/f7/d8/1de3783908658d697a8cfc00582f61299ffba7796d7260c112e4700b1109/librt-0.16.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:6072e92dd876ff6ceeb6cf371e35e51f479349837391341f479b08df4564242b", size = 749569, upload-time = "2026-09-29T00:54:10.702Z" },
- { url = "https://files.pythonhosted.org/packages/b7/32/e817f66c96d6caa8bb8435ff93c4c220624d59efc506be59ab98fcd01d0c/librt-0.16.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:39ca4f2f2fe05de8e63493da592d84311adabe5bef52b193851981da9816b302", size = 729483, upload-time = "2026-09-29T00:54:12.25Z" },
- { url = "https://files.pythonhosted.org/packages/7c/c9/23992ccd2b9d22798fdd0f61353183a47414e651da782ad83eb680f50833/librt-0.16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f9807485a908f00355820f18e91e045ffdcdc5adb68aaec40a1e2b88c5f7bba1", size = 776584, upload-time = "2026-09-29T00:54:13.837Z" },
- { url = "https://files.pythonhosted.org/packages/67/3b/e8af957f08e6e2e8e566b099e43d2748418aa565df6ee1d9d3331209fdfe/librt-0.16.0-cp314-cp314t-win32.whl", hash = "sha256:94be5cb7bca4df6201f4183e9e4fa2086c655283d20b38cd84500a69057575a7", size = 104402, upload-time = "2026-09-29T00:54:15.568Z" },
- { url = "https://files.pythonhosted.org/packages/ca/1c/946e6443d7cd32347a086043395e421e52fd603c9163d2ec970ceab8eed6/librt-0.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d46ca272b251d033dd4527b0dec5f261a28a52bd5fa0f99c117b0a1f8588cc2d", size = 126227, upload-time = "2026-09-29T00:54:17.165Z" },
- { url = "https://files.pythonhosted.org/packages/c2/a3/bc4f9959d3c62bcbf9e5fd3470a8bbd8bf33224ed4c25d7fba173201b8ac/librt-0.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b9d6d4b14e92d876f8026b54c20c445f36425214c1081dc76f74e40db386b82b", size = 116493, upload-time = "2026-09-29T00:54:18.567Z" },
- { url = "https://files.pythonhosted.org/packages/b6/4b/10fdb42dfab4c1533e1570e686b18e86ff4328b406361b39fb3016667638/librt-0.16.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:6fe436af2eaf630474f491af5d032cbe45f93fcff5c3b9fe4ab194a7255b20ff", size = 149829, upload-time = "2026-09-29T00:54:19.933Z" },
- { url = "https://files.pythonhosted.org/packages/8e/30/a90ca13f1d3d91af1680000a4038536907018fbd51764767287a05d28b8b/librt-0.16.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:8ff5d26c529336be9bd7ae04483235d77778ee7d6444a95353102b542601ce81", size = 154522, upload-time = "2026-09-29T00:54:21.306Z" },
- { url = "https://files.pythonhosted.org/packages/5c/dd/bcf364eacfa070bb1fc88d503111ae7177914197ba1fa717c748926e7930/librt-0.16.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:909d8e3c1faee44cb762b1c519ff8613dcc5ceae5c99987a00917b5a31fd1d6a", size = 497593, upload-time = "2026-09-29T00:54:22.798Z" },
- { url = "https://files.pythonhosted.org/packages/18/c1/2c4e81e347bdabfe8346bfc6dc37ae5e154d61c88e30848ec0606025a5e0/librt-0.16.0-cp315-cp315-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:6d4a64283ee61824b5790de882bc68e2d9d7a5143537cb7a966f7354f71646d4", size = 480459, upload-time = "2026-09-29T00:54:24.364Z" },
- { url = "https://files.pythonhosted.org/packages/ab/d7/fef2a3cb8400701be496f6e459876f650b3451f407e30cb243de571ae615/librt-0.16.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5810ba811297fdf37a1531a57667cb8ace0842013ca8606bf9eb7c24cf4be154", size = 507814, upload-time = "2026-09-29T00:54:25.997Z" },
- { url = "https://files.pythonhosted.org/packages/e1/6f/53762927a32e9dc9eb1d1c1f3528da281290c86d96929a8af652f671266e/librt-0.16.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e56aaf8c167548dc8e5d6f3bd0f48dcdd299a23c73be3f744aab79d99e9c7f5d", size = 525114, upload-time = "2026-09-29T00:54:27.747Z" },
- { url = "https://files.pythonhosted.org/packages/6c/67/0b9d031f303c4e8c691a9a8ef9d272f13b530c11cc563b819df621b6a348/librt-0.16.0-cp315-cp315-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f36c58e33b304b525c6c9c5076399c6ebf1109e17b9051a05a407b091b9215b", size = 520465, upload-time = "2026-09-29T00:54:29.331Z" },
- { url = "https://files.pythonhosted.org/packages/ae/d5/2056a3a85864e882eb17a203a10ddb26fa748bc9718ec67e79059ab46cae/librt-0.16.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:242e00b3d4fa37c3d3c1ca5f5c9adb7d909ddb1eac9c41f2787320d00caa0af2", size = 537257, upload-time = "2026-09-29T00:54:30.915Z" },
- { url = "https://files.pythonhosted.org/packages/54/57/e0d79790c163cbc0909e209a6f62e30bb713b647f905a176cdc64848fd4d/librt-0.16.0-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:0253721561787b8df8443eb347b7a6461015354e5bdd37ee38a41fef220d2bb0", size = 527442, upload-time = "2026-09-29T00:54:32.478Z" },
- { url = "https://files.pythonhosted.org/packages/aa/50/1c0c95aba7af51f4752ea34fbf2eb79b36e7cae3a72536248e8c735de735/librt-0.16.0-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:3e483a8d69ede8067db70c0e83007423b6925de6fd53afed01d66160f2e9398c", size = 548139, upload-time = "2026-09-29T00:54:34.183Z" },
- { url = "https://files.pythonhosted.org/packages/98/91/a8a43dd5138d4f55f88846b8f0c85454a4fad69952cebfcff9948f831290/librt-0.16.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:69ba927445cfaaffb4081003ef5224c55a5c2ab67ef956f416ef744916e44121", size = 529708, upload-time = "2026-09-29T00:54:35.761Z" },
- { url = "https://files.pythonhosted.org/packages/2d/41/d5226881ab2b7c20d9d587b37bdd4a0ec8775a96d00ca87ac9f385587db4/librt-0.16.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:d6a365f2ab45a984d0e00eee0dd17f599ceab8cadab6ea07b6111c8132fc0e42", size = 567781, upload-time = "2026-09-29T00:54:37.409Z" },
- { url = "https://files.pythonhosted.org/packages/bb/bf/2345ba57a626e8c78c4ddcc724636a8df5a593fe46c1ee76bbf477e32b0d/librt-0.16.0-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:f01f3805f2dae4781c0c34b440e31740d082950bdaf89a6f601ad589a28af57a", size = 79177, upload-time = "2026-09-29T00:54:38.792Z" },
- { url = "https://files.pythonhosted.org/packages/1e/40/99e77936cc9207f629b077bf7cbc5e2f0827cddd7c29792f04ef881bd2c3/librt-0.16.0-cp315-cp315-win32.whl", hash = "sha256:b0e3e721c75d2e79a76d4422c79d7ba705fe1bbafec907037fe7a657a480a0e3", size = 100127, upload-time = "2026-09-29T00:54:40.251Z" },
- { url = "https://files.pythonhosted.org/packages/56/1e/801fe26bc622061b9dfd010e166d94142cb774b733217e6d98d1c0cf2638/librt-0.16.0-cp315-cp315-win_amd64.whl", hash = "sha256:bc02954b1295de798bbdb0b4e2d8a28c2117de8b5c73dcbeb27dc32572dfb971", size = 120480, upload-time = "2026-09-29T00:54:41.722Z" },
- { url = "https://files.pythonhosted.org/packages/bb/a9/d533983055bd36e112627384c2c038845d1df882540b8bcefb566475640b/librt-0.16.0-cp315-cp315-win_arm64.whl", hash = "sha256:c5db585d43449a5f54303d4b2774e45e1babd975cfe1630a3d708c0b80c3e560", size = 110915, upload-time = "2026-09-29T00:54:43.052Z" },
- { url = "https://files.pythonhosted.org/packages/55/fe/d62238fa9c653b0e0613290349467cb65711b5800e8e474f45e942a0ad95/librt-0.16.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:f06c689cb14afd9b612727553a5ec5a40febf113ca41c4413a2b0b334285884b", size = 159714, upload-time = "2026-09-29T00:54:44.497Z" },
- { url = "https://files.pythonhosted.org/packages/f3/ac/f31efe7818700be72ba4f9af8a80fa67c39808c8d26dacc55dc1f6f35172/librt-0.16.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:13b4e8aba90b0b1c82474e9844aa9ffe7ad3faa484350e1da64cb8188d903134", size = 162086, upload-time = "2026-09-29T00:54:45.968Z" },
- { url = "https://files.pythonhosted.org/packages/d9/16/4d7487bf86a9d7e8e18f37ba5538789233ea693637379b75701bd35ec9de/librt-0.16.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a269c46ae327d8e6f8c1f85f7516cb52c0fa48127565a1105a4f4a05ff2a0b4", size = 712727, upload-time = "2026-09-29T00:54:47.486Z" },
- { url = "https://files.pythonhosted.org/packages/77/74/50cd550ccc1a517b9ed62347625c01ddf8c4afad66490312677c011458f6/librt-0.16.0-cp315-cp315t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a33e0dae1f8592146a4764d54ce842b278732d21a84e17c3bbe6b1bc158a2248", size = 681106, upload-time = "2026-09-29T00:54:49.126Z" },
- { url = "https://files.pythonhosted.org/packages/df/5d/7293f712975ee6fdd2251411fd9ef1c62bc99c7b83ecbe62dbc999b1fab7/librt-0.16.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:47ada6ea32636492c61aa8ad27ae3b9404bfe7a97e3ba946d1984236cc741da0", size = 705921, upload-time = "2026-09-29T00:54:50.905Z" },
- { url = "https://files.pythonhosted.org/packages/67/f7/8aab946f11d59d1bece9ffc65d994b200c789a8ca5a95c17e17e609f912a/librt-0.16.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43bd6e642d8a248c114327f98dd25ac5a7cb5aa168ef02f0559b91874df16b8", size = 731786, upload-time = "2026-09-29T00:54:52.584Z" },
- { url = "https://files.pythonhosted.org/packages/43/76/1c42ab31e7cb8384ebf6d3af607213c495222474f4940443ae7639ab7685/librt-0.16.0-cp315-cp315t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c3d1bb7841a816ace6449bb26d3f9560dbfa20e71c568d23f0f62bf1e68f50b1", size = 744964, upload-time = "2026-09-29T00:54:54.217Z" },
- { url = "https://files.pythonhosted.org/packages/6f/2e/4b19982d933d2dfced671e840b219e4c1cd3f507df6e6dabb51bbd4e3850/librt-0.16.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:3931f7a3db322e7f44e02a280e3949326ce9579ad388ee8d691dc7c76da9fb70", size = 765305, upload-time = "2026-09-29T00:54:55.815Z" },
- { url = "https://files.pythonhosted.org/packages/b8/1b/e872583de2dcb3ac7746e7a2321aeeb168274f3952a87dc66e05ddb29faf/librt-0.16.0-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:f4462528b6000afe8f16907b5c7c2553abf1df005ba5140e6eb394541c3624c3", size = 744959, upload-time = "2026-09-29T00:54:57.532Z" },
- { url = "https://files.pythonhosted.org/packages/10/de/a18c6bcfb297af2674233e90b3c3661c3a0af0f1434a0c966d2ef708a835/librt-0.16.0-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:80039ba9b6a7d5f1a0175a4cca6bbefead87bd854c80abad1cb30afe47a830db", size = 758138, upload-time = "2026-09-29T00:54:59.484Z" },
- { url = "https://files.pythonhosted.org/packages/22/1c/0df1d732539c297bb1a093e1fe3204d2faf76a9022cf4e1d1c2fce059790/librt-0.16.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:7a1d272724b581bb6bc769dfdafed6da2ecc9886ba2450311de55a4ac2e1e9cd", size = 744268, upload-time = "2026-09-29T00:55:01.179Z" },
- { url = "https://files.pythonhosted.org/packages/a2/f7/ccaf31331f20c91a5bd9bd48ffc3f743f9c81bb5720cc7b2a720ca204f0a/librt-0.16.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:fbe4fb8c5445f7496d7f7f6bb0807875d09d47e6771ffa175fb2df2895fb86ba", size = 785783, upload-time = "2026-09-29T00:55:02.938Z" },
- { url = "https://files.pythonhosted.org/packages/c1/ea/7421d9e6db894cd6cd788b604b3394a163ded6b942052b6f96c23ccf08e1/librt-0.16.0-cp315-cp315t-win32.whl", hash = "sha256:375bfe6b572a8f6cfc398709356046173bf27e64c4c5edaf5f7062f051fb4bf9", size = 104355, upload-time = "2026-09-29T00:55:04.533Z" },
- { url = "https://files.pythonhosted.org/packages/85/6d/7c31a506eb847bc58aeb2402d58e17605e821f68ab5df5e66fe274a6df41/librt-0.16.0-cp315-cp315t-win_amd64.whl", hash = "sha256:bd3150023d3dc2bc70f3784e59ffa1140d56ddba3d8125b3d6f9f85221279bfc", size = 126134, upload-time = "2026-09-29T00:55:06.017Z" },
- { url = "https://files.pythonhosted.org/packages/36/69/7a5d10ac409c4da0355e054a14371871da9b5557fcc42772cd00181c6cce/librt-0.16.0-cp315-cp315t-win_arm64.whl", hash = "sha256:8ceafb70f2a4f0826f11031942e59c0728fd98da112dc346d4352bde1e486866", size = 116391, upload-time = "2026-09-29T00:55:07.484Z" },
-]
-
[[package]]
name = "markdown"
version = "3.11.2"
@@ -1679,69 +1485,41 @@ wheels = [
[[package]]
name = "mypy"
-version = "2.4.0"
+version = "1.17.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "ast-serialize" },
- { name = "librt", marker = "platform_python_implementation != 'PyPy'" },
{ name = "mypy-extensions" },
{ name = "pathspec" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/34/4e/64300736cf0a0373a27b94a91b664ee7382e36f77b0621bae6381da3e180/mypy-2.4.0.tar.gz", hash = "sha256:77bdaebd452f43fcfc4cc3ba94352a3ea537cd01e3f2d0879f48673d2ec00d6e", size = 4064425, upload-time = "2026-10-01T20:40:39.229Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/5c/0a/78c9ac93933e2ecd51d807021f78c03ffe98640f33951a8be159f5fd55bc/mypy-2.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffda5244fd1ad71a1e54405e35f50d09b80c3978efa120f58bd1252ae32c62d2", size = 14408618, upload-time = "2026-10-01T20:39:46.766Z" },
- { url = "https://files.pythonhosted.org/packages/aa/4f/5f5f4429fd5a4218ce6268164a66afeed75c7e2ce5522a4d163faaa5fe85/mypy-2.4.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:615b03922d40e186fd1df73156473db0ba525c639bb4fd1cf28e1694879c9b23", size = 14552749, upload-time = "2026-10-01T20:40:08.325Z" },
- { url = "https://files.pythonhosted.org/packages/54/78/fa1c319cd51db7a6027b46117460d5a361c6bba0c0f0f0670a2dab8ce2a5/mypy-2.4.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13fa24f439c0e48a290a3922fa14ccd22f2762bae99d2142931b3e40a9055080", size = 15505846, upload-time = "2026-10-01T20:39:15.475Z" },
- { url = "https://files.pythonhosted.org/packages/e2/5a/14c02690776fb04e9fa60b81173cb9ae6315e0745fad3813b303737f2f0c/mypy-2.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3adef556a19eb3b630bf86a79c29d7da3d61e541472d0d897ca01b171c0abf8f", size = 15823059, upload-time = "2026-10-01T20:39:35.429Z" },
- { url = "https://files.pythonhosted.org/packages/76/d8/d29afa819f8cb765018d6e2773e390a41ea4592f75abe417135969646857/mypy-2.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:1dc0f64b0a92ae27a49d2175f0bacfa56e15bdc5b420cf92e0c1f79292219cbb", size = 11312927, upload-time = "2026-10-01T20:39:28.509Z" },
- { url = "https://files.pythonhosted.org/packages/bc/c8/966dec4d80303855d824e6bf67fe1da812d88adfe4d75e24d8545dab4826/mypy-2.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:4a378fc15fb33e321f04652c166ce73eeb8833a97c3d218132844e938cd93220", size = 10879252, upload-time = "2026-10-01T20:38:41.545Z" },
- { url = "https://files.pythonhosted.org/packages/26/67/e53965e67d23d48e78dd603aa7525a8d77e7818c23954cc217ccde5cd5c0/mypy-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d20e6c7c35fcbf2a0ebdd0eaeacfbc243009dfd33ab7822d54e213912e6dbbd", size = 14315745, upload-time = "2026-10-01T20:39:25.03Z" },
- { url = "https://files.pythonhosted.org/packages/b1/5d/a588ccd57625822d8484d7aff4e9c70f88d042a91c2ec84324a2bc14b3f3/mypy-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3011537be6cf1de4511c0255a324362a812b58184bbe61e15f59c8b31033bd74", size = 14465652, upload-time = "2026-10-01T20:38:57.19Z" },
- { url = "https://files.pythonhosted.org/packages/05/af/b4978a5566cb829ca563dc08e3ad8d944f116078786ad54ba3dff2bcb64d/mypy-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a96b07a49b7b1d025ce59c1b3acbcf24bead9a83da4523c4a6bde1bb94e7a0e1", size = 15419180, upload-time = "2026-10-01T20:40:27.808Z" },
- { url = "https://files.pythonhosted.org/packages/b2/8c/52c71f31c54d0a749d832cc73b933c6e38c52d9b270d5503e031c73723dc/mypy-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7c4f8f8d1d1c0e2832d8ee7113dd08f6df6c7aad9e863fcbed9f25832be0b8c4", size = 15669212, upload-time = "2026-10-01T20:39:20.443Z" },
- { url = "https://files.pythonhosted.org/packages/e8/55/ba63121494404fd7d3787ec176ccd02820d6ec125ac4e7c4c0b79f2cc6c7/mypy-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba05652540bf12828e52abae807b024b09ca144ff4f75e2450a81d69c376425b", size = 11299572, upload-time = "2026-10-01T20:40:03.627Z" },
- { url = "https://files.pythonhosted.org/packages/9c/9c/385c81b5c0d3a2917f077c4da660d0493f9521ccfd4b058e33b860f72d02/mypy-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:6306086b87cf7f8a29aa618d9fd9bffb56c59247166b9660fdb54d86d7714ecd", size = 10869751, upload-time = "2026-10-01T20:40:25.483Z" },
- { url = "https://files.pythonhosted.org/packages/68/ed/e5d7cf4017e74a1c1e1c4058ce8f614fc1e3e7606564f47166e22bfc9f95/mypy-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e05ff2925d8b37ad26c80c1b9dc43ae5d455da2df1e23c24c095a6425917c57e", size = 14378593, upload-time = "2026-10-01T20:38:48.222Z" },
- { url = "https://files.pythonhosted.org/packages/30/7d/12d994886a922f0f1997becc9c6625198d61eeb5962558a886af7dd38d54/mypy-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29243242cf72582b65f9582ad9e56e8cb281566ed3519f4cd70bb8b9f2977e90", size = 14464697, upload-time = "2026-10-01T20:39:11.059Z" },
- { url = "https://files.pythonhosted.org/packages/f3/9e/bcc9af755425ad17790bf11d73c2ea7592914cf309ee1340997670f9d57a/mypy-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29eb0b9427a6b11b992e452f6cceb8af724f4dceb47e779d0b35e405e996ea5e", size = 15552257, upload-time = "2026-10-01T20:39:56.269Z" },
- { url = "https://files.pythonhosted.org/packages/af/0c/3343fc4525d6f00d75ad17a93a9f052d5163641cb8911840d4a12cb59ff2/mypy-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e3ebe2f72a2a1156065a9851570ffbf50c0a93cdccadef9c6e05c508a4fd10b1", size = 15800443, upload-time = "2026-10-01T20:38:54.815Z" },
- { url = "https://files.pythonhosted.org/packages/8f/0e/69aac6b8159da7c53e6115a2be1bf48e85503402cadbf506dd708e8b2ad7/mypy-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:236e0d68f6941992b0811128e652590f590db444ab29ad8f1324765b9298b946", size = 11381574, upload-time = "2026-10-01T20:39:49.406Z" },
- { url = "https://files.pythonhosted.org/packages/6e/d3/d32ce4feb5993eec09d2024bef16cedc9b93701b1446b86092f08b24491b/mypy-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:82d0f94c8587ccb472622ee7795280aaa38a06640d5f45b3f16909d6dd86a989", size = 10944729, upload-time = "2026-10-01T20:40:15.203Z" },
- { url = "https://files.pythonhosted.org/packages/44/f2/eb15183c97c69d7cbfac990a6efd33a19ecfd97dab9e714c742fa78a784f/mypy-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7da85fbcff6dac1abcc636707bed38b45598131fb7a605d9719c70b5cc733af8", size = 14213395, upload-time = "2026-10-01T20:40:21.178Z" },
- { url = "https://files.pythonhosted.org/packages/8c/b5/ba91b6ff65e4d6b6ff53b2b3b3ac5f1babf0c7c27d0b43a0196b1c967926/mypy-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4209da39d85cf240f762af622d8180fcdfcb4727d021f44ade62d613a1a43324", size = 14373009, upload-time = "2026-10-01T20:39:58.86Z" },
- { url = "https://files.pythonhosted.org/packages/d5/c4/484275efc935c0003e55e4e8a33e4b8e99528ee956c12256ece4708f903f/mypy-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6be721bd4bd57576193653b75b4af3461c9d0bf7dd8b528f782e9be210dc75bb", size = 15465557, upload-time = "2026-10-01T20:39:01.794Z" },
- { url = "https://files.pythonhosted.org/packages/50/30/66eb6fdd0875e3c9025a02f0bb0ea2e524b74274b658c37fde0068c4939d/mypy-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1fde197ae65be856a034a91b70ed747a16562ca69577785f06c661548424bf1", size = 15728673, upload-time = "2026-10-01T20:38:43.724Z" },
- { url = "https://files.pythonhosted.org/packages/58/bc/2aa98fd7f49c42dba8e9c065886fadffdd00f3c654cff3f2a7103a797ac8/mypy-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:295ecf2e57542cd836ca537486951289678c8c7d1ee6ad74ebe29b2168a003cf", size = 11389803, upload-time = "2026-10-01T20:39:37.547Z" },
- { url = "https://files.pythonhosted.org/packages/49/41/17b60df2d946792ef6af43b89351f5c9ddabc69053206b2304945572a744/mypy-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:bc378bdad4e9f12b5bd96466083d1e71acf00594ec9c7b2bdb5e02816f77f303", size = 10951060, upload-time = "2026-10-01T20:39:04.015Z" },
- { url = "https://files.pythonhosted.org/packages/e0/66/924be0b653372ed31ad5c48e26044cc00840e591943a56e61621cf05b60e/mypy-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:058165f564ccf559c68c70fec2091fca5891110480210c22594635e3f6683437", size = 14234230, upload-time = "2026-10-01T20:39:44.535Z" },
- { url = "https://files.pythonhosted.org/packages/56/39/c4f176880a4177123576de6cec6309feea8f42fca2bf2f6584e88054f656/mypy-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fa247e02b505a45a2775f69df38d360d197e3790bc60f717595db9eda358b6e", size = 14392944, upload-time = "2026-10-01T20:40:05.78Z" },
- { url = "https://files.pythonhosted.org/packages/bc/1c/26e16977e25ef2494a74f8ffc872a76ec2c3aa43c3156057cbdf88f352c5/mypy-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20e9a5cd875837520c43db98dea0b6d0c2197833d95c30127d8f570fb9b1f00b", size = 15441463, upload-time = "2026-10-01T20:40:32.412Z" },
- { url = "https://files.pythonhosted.org/packages/31/9c/9e4b049f0ecefbfd6817ca2a16eea55dd74a07950268edc6cffd29ccfe98/mypy-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9f03a7828cca2b0adcd6662aee8f2711ff8830e1027641fdea3ab0b787483566", size = 15705406, upload-time = "2026-10-01T20:40:34.844Z" },
- { url = "https://files.pythonhosted.org/packages/4a/5e/e861b5f6c5ef9ee6cd24683aed1edecf82a26dbd536049f9b7850b586267/mypy-2.4.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:9279488933040b638c0ab739084c0ca100efeea6db581bf5d7628d8e89de53fe", size = 7897523, upload-time = "2026-10-01T20:39:39.861Z" },
- { url = "https://files.pythonhosted.org/packages/3a/87/61ffee58b25a956532a6006fae84918a2de849ed25f397492b2e815fe7b3/mypy-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:2106b55105ba5ea9be4f53a24517fc5fa927ff1585edc9bc1a975abb72caef89", size = 11576345, upload-time = "2026-10-01T20:39:41.553Z" },
- { url = "https://files.pythonhosted.org/packages/a1/88/a331c20698971c2ce8d1c30f317fd61b5be13a85b1f053e12dfa22ac6568/mypy-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:528c8744b8b5e3ecb8774f86af38d2376216816e9908317ad055f3c9c2d74799", size = 11172062, upload-time = "2026-10-01T20:38:59.534Z" },
- { url = "https://files.pythonhosted.org/packages/47/c0/4f7daa73270dced8e86c6f4a911c68082d03a84e6c1ec9bd916028d66131/mypy-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0bb95cf34899e4619c61ab0a8667804e139e580b30d5df12af2102dfe44d0c97", size = 15711208, upload-time = "2026-10-01T20:39:30.597Z" },
- { url = "https://files.pythonhosted.org/packages/2f/05/f1afa303c678be24cf7a266d38fb24b3de4599a024c2f9ba0d5905a3efa3/mypy-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:86d616fe84c6eab8026f8c50ab5bcb90db780d2ccd233d971e34e92bede9b359", size = 15935442, upload-time = "2026-10-01T20:39:22.781Z" },
- { url = "https://files.pythonhosted.org/packages/9d/d6/6a1a45459b63716e0d035f4892a926d3054f0a8cbfa02551d228a9946083/mypy-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3f86fd1313dd69d013e265f1fdcd12ea7a9d606f9875b2a3db946cd334555f3", size = 17240044, upload-time = "2026-10-01T20:39:32.95Z" },
- { url = "https://files.pythonhosted.org/packages/87/85/ae33bee66c13f98d421964d87bf0888be941063bc75c1204a4cf142cf1cc/mypy-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:720434d48542ecfe84d32d287b727569d3fc8f5769acd39051130e490a5c295c", size = 17545856, upload-time = "2026-10-01T20:39:08.827Z" },
- { url = "https://files.pythonhosted.org/packages/df/de/eeff209b65c3e267818d79333bb9f058da6e4e73761461bd94748a2eb628/mypy-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:a6e851b82c0661f69f1630fc16172c68787a6a9cf0991e7c6437d60976cdcd76", size = 12369342, upload-time = "2026-10-01T20:40:10.516Z" },
- { url = "https://files.pythonhosted.org/packages/2b/43/e62d8d5c1dd737aa248968302ff7d6eabf997c3301773ed8bcb64932ca77/mypy-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:3bd0e340f0ebe65c548210f53be3fd8192e83964760caf0c28bef368e68b0d37", size = 11849099, upload-time = "2026-10-01T20:39:51.834Z" },
- { url = "https://files.pythonhosted.org/packages/2e/5f/335b8980055118dc131355155883fb676bb2161a0d97e08658e479c776fb/mypy-2.4.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:afa89837d9be67e0cadfa33bca3bb7efdda98c3b07e74dc3b635ebfb1c8a926a", size = 14241302, upload-time = "2026-10-01T20:39:13.227Z" },
- { url = "https://files.pythonhosted.org/packages/18/37/1482fdc49332b145828912b15f16eee0c4ca70a8bce0f6514ece79b14680/mypy-2.4.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb443e81057896132d3642d6be219e6efd158691ac7883e3ba8fcb469865f05d", size = 14408200, upload-time = "2026-10-01T20:38:52.576Z" },
- { url = "https://files.pythonhosted.org/packages/04/09/dce2e8f6c1b31053c430ef6963f6f7a38ccb49b90c5e01e37ed0129b7d5a/mypy-2.4.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7f38f57d344f8b6accb40e01c3d83cfc590498231724d16c07ffb7940f157818", size = 15444409, upload-time = "2026-10-01T20:39:18.1Z" },
- { url = "https://files.pythonhosted.org/packages/43/c5/91b68306da4cd280cb15be35dbb5ffc343cabd67c4b121b6625bf2d13177/mypy-2.4.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e76172710bd4e5eeae061abfd68347e5264632e02778be61784671ae3a2132f5", size = 15731186, upload-time = "2026-10-01T20:40:23.426Z" },
- { url = "https://files.pythonhosted.org/packages/64/71/2d0340182a8f27352fb1e25531355951108b506097c433937e9eec452ffd/mypy-2.4.0-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:f83353e47ab520bf6fd4df8f5897d9fe081211f2fbc4b7d37736a3e3c166cbcf", size = 7897838, upload-time = "2026-10-01T20:38:50.779Z" },
- { url = "https://files.pythonhosted.org/packages/3e/08/32703c117e134c02efa2a82705bb40cee91eaedea10b6530e20eba651b1f/mypy-2.4.0-cp315-cp315-win_amd64.whl", hash = "sha256:970b221ed5842213d98e3c480c08f795ace4b1f81fb21e1b126bd0476bce1c34", size = 11575700, upload-time = "2026-10-01T20:39:53.982Z" },
- { url = "https://files.pythonhosted.org/packages/ab/08/08bb269feafdaad031046ee2d771528a64467e6a180f962afc28c4a3ccd8/mypy-2.4.0-cp315-cp315-win_arm64.whl", hash = "sha256:502b94b0b331f7dafe32fd6b151797ddbb4f32385b362e722c783a025e5954a3", size = 11172908, upload-time = "2026-10-01T20:40:12.91Z" },
- { url = "https://files.pythonhosted.org/packages/fb/3f/c5c92626006ca92c7686adfbece47fc6a0daa0e54753952a9ad13ce0561c/mypy-2.4.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:c9de622fd397495695d0598ddc789222bfcfec9d7c9ec3a1e385c855e3bc5e01", size = 15705970, upload-time = "2026-10-01T20:40:17.79Z" },
- { url = "https://files.pythonhosted.org/packages/10/f3/863365f7997a76a5a1dd42d7902ab05afa4124e8419089cbca1ce2554db1/mypy-2.4.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f459f0b4f0596d9d51fe7716b404b35287b99e77da98a7af90a65dd5fd61141", size = 15942234, upload-time = "2026-10-01T20:40:37.243Z" },
- { url = "https://files.pythonhosted.org/packages/f6/30/2f45b1f425a2c95dbe1a3f4d076bfd42b76e9615dba5906230703b14e4eb/mypy-2.4.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9b028548b3af480e2b1ed8df14ccaac86f99c9f600d1580770ab7ba3dcd40f0", size = 17218364, upload-time = "2026-10-01T20:39:06.497Z" },
- { url = "https://files.pythonhosted.org/packages/43/8b/5b2bbfc69e84800b78fa2dba16d995b003f93b573558e412c5490d6e6c37/mypy-2.4.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:cb734b2668c1f40d07ce093bbeb4407e9527c67901627b0e1679825be3f09975", size = 17557934, upload-time = "2026-10-01T20:38:46.077Z" },
- { url = "https://files.pythonhosted.org/packages/df/c0/1a5dc601c22041a7bbfe1ff71cf097fbbd4fb49930867c6789baf5102106/mypy-2.4.0-cp315-cp315t-win_amd64.whl", hash = "sha256:172e30b8fea631fe310f0c665477f52d9ea40bb4e99e0c81dc30118563b13710", size = 12350955, upload-time = "2026-10-01T20:40:01.52Z" },
- { url = "https://files.pythonhosted.org/packages/1c/bc/697e9e26fc2a86c094ad67ee1e419971b7f01ded66ee66231b09e9f3e12e/mypy-2.4.0-cp315-cp315t-win_arm64.whl", hash = "sha256:5786ef987b3767e51aaa53f20aec104c0252b42ecda7aef8e8b4cbae279b05c5", size = 11842738, upload-time = "2026-10-01T20:40:30.026Z" },
- { url = "https://files.pythonhosted.org/packages/81/12/46ae8670c98a3cd0286ca5645c2f918f8f6be65edfed81b916010619f668/mypy-2.4.0-py3-none-any.whl", hash = "sha256:d01c5d26a352acc6d5cf3128225477e1e8465e8d3029d4c345807fbf7f3cf093", size = 2800734, upload-time = "2026-10-01T20:39:26.837Z" },
+sdist = { url = "https://files.pythonhosted.org/packages/1e/e3/034322d5a779685218ed69286c32faa505247f1f096251ef66c8fd203b08/mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03", size = 3352114, upload-time = "2025-07-14T20:34:30.181Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6a/31/e762baa3b73905c856d45ab77b4af850e8159dffffd86a52879539a08c6b/mypy-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8e08de6138043108b3b18f09d3f817a4783912e48828ab397ecf183135d84d6", size = 10998313, upload-time = "2025-07-14T20:33:24.519Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/c1/25b2f0d46fb7e0b5e2bee61ec3a47fe13eff9e3c2f2234f144858bbe6485/mypy-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce4a17920ec144647d448fc43725b5873548b1aae6c603225626747ededf582d", size = 10128922, upload-time = "2025-07-14T20:34:06.414Z" },
+ { url = "https://files.pythonhosted.org/packages/02/78/6d646603a57aa8a2886df1b8881fe777ea60f28098790c1089230cd9c61d/mypy-1.17.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ff25d151cc057fdddb1cb1881ef36e9c41fa2a5e78d8dd71bee6e4dcd2bc05b", size = 11913524, upload-time = "2025-07-14T20:33:19.109Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/19/dae6c55e87ee426fb76980f7e78484450cad1c01c55a1dc4e91c930bea01/mypy-1.17.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93468cf29aa9a132bceb103bd8475f78cacde2b1b9a94fd978d50d4bdf616c9a", size = 12650527, upload-time = "2025-07-14T20:32:44.095Z" },
+ { url = "https://files.pythonhosted.org/packages/86/e1/f916845a235235a6c1e4d4d065a3930113767001d491b8b2e1b61ca56647/mypy-1.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:98189382b310f16343151f65dd7e6867386d3e35f7878c45cfa11383d175d91f", size = 12897284, upload-time = "2025-07-14T20:33:38.168Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/dc/414760708a4ea1b096bd214d26a24e30ac5e917ef293bc33cdb6fe22d2da/mypy-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:c004135a300ab06a045c1c0d8e3f10215e71d7b4f5bb9a42ab80236364429937", size = 9506493, upload-time = "2025-07-14T20:34:01.093Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/24/82efb502b0b0f661c49aa21cfe3e1999ddf64bf5500fc03b5a1536a39d39/mypy-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d4fe5c72fd262d9c2c91c1117d16aac555e05f5beb2bae6a755274c6eec42be", size = 10914150, upload-time = "2025-07-14T20:31:51.985Z" },
+ { url = "https://files.pythonhosted.org/packages/03/96/8ef9a6ff8cedadff4400e2254689ca1dc4b420b92c55255b44573de10c54/mypy-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96b196e5c16f41b4f7736840e8455958e832871990c7ba26bf58175e357ed61", size = 10039845, upload-time = "2025-07-14T20:32:30.527Z" },
+ { url = "https://files.pythonhosted.org/packages/df/32/7ce359a56be779d38021d07941cfbb099b41411d72d827230a36203dbb81/mypy-1.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73a0ff2dd10337ceb521c080d4147755ee302dcde6e1a913babd59473904615f", size = 11837246, upload-time = "2025-07-14T20:32:01.28Z" },
+ { url = "https://files.pythonhosted.org/packages/82/16/b775047054de4d8dbd668df9137707e54b07fe18c7923839cd1e524bf756/mypy-1.17.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24cfcc1179c4447854e9e406d3af0f77736d631ec87d31c6281ecd5025df625d", size = 12571106, upload-time = "2025-07-14T20:34:26.942Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/cf/fa33eaf29a606102c8d9ffa45a386a04c2203d9ad18bf4eef3e20c43ebc8/mypy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56f180ff6430e6373db7a1d569317675b0a451caf5fef6ce4ab365f5f2f6c3", size = 12759960, upload-time = "2025-07-14T20:33:42.882Z" },
+ { url = "https://files.pythonhosted.org/packages/94/75/3f5a29209f27e739ca57e6350bc6b783a38c7621bdf9cac3ab8a08665801/mypy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:eafaf8b9252734400f9b77df98b4eee3d2eecab16104680d51341c75702cad70", size = 9503888, upload-time = "2025-07-14T20:32:34.392Z" },
+ { url = "https://files.pythonhosted.org/packages/12/e9/e6824ed620bbf51d3bf4d6cbbe4953e83eaf31a448d1b3cfb3620ccb641c/mypy-1.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f986f1cab8dbec39ba6e0eaa42d4d3ac6686516a5d3dccd64be095db05ebc6bb", size = 11086395, upload-time = "2025-07-14T20:34:11.452Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/51/a4afd1ae279707953be175d303f04a5a7bd7e28dc62463ad29c1c857927e/mypy-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:51e455a54d199dd6e931cd7ea987d061c2afbaf0960f7f66deef47c90d1b304d", size = 10120052, upload-time = "2025-07-14T20:33:09.897Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/71/19adfeac926ba8205f1d1466d0d360d07b46486bf64360c54cb5a2bd86a8/mypy-1.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3204d773bab5ff4ebbd1f8efa11b498027cd57017c003ae970f310e5b96be8d8", size = 11861806, upload-time = "2025-07-14T20:32:16.028Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/64/d6120eca3835baf7179e6797a0b61d6c47e0bc2324b1f6819d8428d5b9ba/mypy-1.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1051df7ec0886fa246a530ae917c473491e9a0ba6938cfd0ec2abc1076495c3e", size = 12744371, upload-time = "2025-07-14T20:33:33.503Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/dc/56f53b5255a166f5bd0f137eed960e5065f2744509dfe69474ff0ba772a5/mypy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f773c6d14dcc108a5b141b4456b0871df638eb411a89cd1c0c001fc4a9d08fc8", size = 12914558, upload-time = "2025-07-14T20:33:56.961Z" },
+ { url = "https://files.pythonhosted.org/packages/69/ac/070bad311171badc9add2910e7f89271695a25c136de24bbafc7eded56d5/mypy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:1619a485fd0e9c959b943c7b519ed26b712de3002d7de43154a489a2d0fd817d", size = 9585447, upload-time = "2025-07-14T20:32:20.594Z" },
+ { url = "https://files.pythonhosted.org/packages/be/7b/5f8ab461369b9e62157072156935cec9d272196556bdc7c2ff5f4c7c0f9b/mypy-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c41aa59211e49d717d92b3bb1238c06d387c9325d3122085113c79118bebb06", size = 11070019, upload-time = "2025-07-14T20:32:07.99Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/f8/c49c9e5a2ac0badcc54beb24e774d2499748302c9568f7f09e8730e953fa/mypy-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e69db1fb65b3114f98c753e3930a00514f5b68794ba80590eb02090d54a5d4a", size = 10114457, upload-time = "2025-07-14T20:33:47.285Z" },
+ { url = "https://files.pythonhosted.org/packages/89/0c/fb3f9c939ad9beed3e328008b3fb90b20fda2cddc0f7e4c20dbefefc3b33/mypy-1.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03ba330b76710f83d6ac500053f7727270b6b8553b0423348ffb3af6f2f7b889", size = 11857838, upload-time = "2025-07-14T20:33:14.462Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/66/85607ab5137d65e4f54d9797b77d5a038ef34f714929cf8ad30b03f628df/mypy-1.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037bc0f0b124ce46bfde955c647f3e395c6174476a968c0f22c95a8d2f589bba", size = 12731358, upload-time = "2025-07-14T20:32:25.579Z" },
+ { url = "https://files.pythonhosted.org/packages/73/d0/341dbbfb35ce53d01f8f2969facbb66486cee9804048bf6c01b048127501/mypy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c38876106cb6132259683632b287238858bd58de267d80defb6f418e9ee50658", size = 12917480, upload-time = "2025-07-14T20:34:21.868Z" },
+ { url = "https://files.pythonhosted.org/packages/64/63/70c8b7dbfc520089ac48d01367a97e8acd734f65bd07813081f508a8c94c/mypy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:d30ba01c0f151998f367506fab31c2ac4527e6a7b2690107c7a7f9e3cb419a9c", size = 9589666, upload-time = "2025-07-14T20:34:16.841Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/fc/ee058cc4316f219078464555873e99d170bde1d9569abd833300dbeb484a/mypy-1.17.0-py3-none-any.whl", hash = "sha256:15d9d0018237ab058e5de3d8fce61b6fa72cc59cc78fd91f1b474bce12abf496", size = 2283195, upload-time = "2025-07-14T20:31:54.753Z" },
]
[[package]]
@@ -1788,6 +1566,7 @@ dev = [
{ name = "dirty-equals" },
{ name = "httpx-aiohttp" },
{ name = "mypy" },
+ { name = "pydantic" },
{ name = "pyright" },
{ name = "pytest" },
{ name = "pytest-asyncio" },
@@ -1823,7 +1602,8 @@ provides-extras = ["aiohttp", "pty"]
dev = [
{ name = "dirty-equals", specifier = ">=0.6" },
{ name = "httpx-aiohttp", specifier = ">=0.1.9" },
- { name = "mypy", specifier = ">=1.18" },
+ { name = "mypy", specifier = "==1.17.0" },
+ { name = "pydantic", specifier = "<2.13" },
{ name = "pyright", specifier = "==1.1.399" },
{ name = "pytest", specifier = ">=8.4" },
{ name = "pytest-asyncio", specifier = ">=1" },
@@ -2143,7 +1923,7 @@ wheels = [
[[package]]
name = "pydantic"
-version = "2.14.1"
+version = "2.12.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
@@ -2151,156 +1931,127 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/7c/0b/8e10b2e693af8ec54346a14caf36221334775975a747c9ceaa3f8371d96d/pydantic-2.14.1.tar.gz", hash = "sha256:94f478203dd03404682a1ada216965651dd74b1d2d5ffd62e00e0837caab5c26", size = 900362, upload-time = "2026-10-11T18:37:55.396Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/35/ea/a56b9fe5066f3537b7882f77e9c5dfb26d8c2eefdaed9b5fc73d57b4dc22/pydantic-2.14.1-py3-none-any.whl", hash = "sha256:9195d967ec791692a04438115466764fb8b9a27b31f14a760437694f40d6b454", size = 488106, upload-time = "2026-10-11T18:37:53.437Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
]
[[package]]
name = "pydantic-core"
-version = "2.50.1"
+version = "2.41.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/a6/24/af4ec4be49fbc810f35b0bdcacc3d433b56bbfa469fd3bfd4ae116cd9bf1/pydantic_core-2.50.1.tar.gz", hash = "sha256:e50d7b94baac6c7d09927fa5ca5800a0c7ee5015c7fcff65beb3a1931b5a6e09", size = 513515, upload-time = "2026-10-11T18:35:44.82Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/09/db/c9cc34707ba2c23ce90d54e8a4ee9307583ad054b9df2ce570ba550cb576/pydantic_core-2.50.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b281a3b0f0822618fe5e3e0d8a2048b6356b14388505dc9374ccffeb69989713", size = 2154222, upload-time = "2026-10-11T18:31:38.345Z" },
- { url = "https://files.pythonhosted.org/packages/c1/17/6963f4ee55b7df7658f5c036c0f90baf8cd3d413f5d8b551f227e8467afc/pydantic_core-2.50.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fa4c8bc12c1354c5550c0c35c1852c8c1901e89e06561724e03f8d0342e1f87", size = 2048126, upload-time = "2026-10-11T18:31:41.061Z" },
- { url = "https://files.pythonhosted.org/packages/bf/be/f75637e222085d29a3076edbc5cfd0dd9b77d145e310ff7dc1150f07558a/pydantic_core-2.50.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3aa9de446b793de2beb6fa2d9d0961803126c4e2a99c2f25ab59b9fd6ea125c0", size = 2052460, upload-time = "2026-10-11T18:31:42.321Z" },
- { url = "https://files.pythonhosted.org/packages/7a/1e/3d192ffa39ce3bd0e98e988fb5c055cfbf4af6e6e155a4ca5baa86d1f2cc/pydantic_core-2.50.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de531ce1e2a3364e8767878b58f4ff728a434b4fde089781fe30b1e08e2396e0", size = 2190750, upload-time = "2026-10-11T18:31:43.572Z" },
- { url = "https://files.pythonhosted.org/packages/b2/7e/12ea94df5220ab316ee5bb498ebcf710cff2db352653039fb6033f6997d1/pydantic_core-2.50.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7c58106de36ac6a56314182958de20db8d3a29dfd5db527192cc754e4f8e7fb", size = 2390043, upload-time = "2026-10-11T18:31:44.745Z" },
- { url = "https://files.pythonhosted.org/packages/f6/bc/4fbb6dc90af038628ec824434a9841be9d46854f9267bb658f09e315024d/pydantic_core-2.50.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b4c3df25bd323bf1d36a648d563cf1fc69d717451569927151bdad7cad07a77", size = 2432397, upload-time = "2026-10-11T18:31:46.186Z" },
- { url = "https://files.pythonhosted.org/packages/1b/1a/21a58791021397d9ca35295e3f4291c16e319155c5904334b89eb07e97e1/pydantic_core-2.50.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f77ac30b19221cd9bd3fcfa3d4614eff93140d0572ab730cded17b64adca05f3", size = 2160719, upload-time = "2026-10-11T18:31:47.557Z" },
- { url = "https://files.pythonhosted.org/packages/73/6a/d5b2c5876162fd2c444eaed0064b5d9d9d0ef0d31530fc6a7997e8685ad8/pydantic_core-2.50.1-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:d939de9c82e2126f7f48a7e658f8a85ed46d57662d53f44c49b8895fe94a3eb7", size = 2256164, upload-time = "2026-10-11T18:31:48.797Z" },
- { url = "https://files.pythonhosted.org/packages/af/e8/1a6625d6e99250388dd0ae817c5d75839cd6103e7b790f59e0ffff74955d/pydantic_core-2.50.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:30ddf019d082c117b5d309e5b86710c2a78909907ec1a9381feec3eec02eca0b", size = 2271176, upload-time = "2026-10-11T18:31:49.987Z" },
- { url = "https://files.pythonhosted.org/packages/19/9f/bbdd137d7ec41284701dab4d1489ea597d401dff2f5f3e6b41ea872e0b12/pydantic_core-2.50.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2ab756b72bd5054e4c7ef3ded331b35786cbd3cf931531a508f79a9537517064", size = 2304637, upload-time = "2026-10-11T18:31:51.251Z" },
- { url = "https://files.pythonhosted.org/packages/49/5d/7bd322e649b41b45130bc00bfa34dc57498c853aa8e6fc9604e33bc7087c/pydantic_core-2.50.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:b087b1c5be7ac687cf22eabfe4b6b608d40df23610651e93611e1f49118baf84", size = 2467374, upload-time = "2026-10-11T18:31:52.587Z" },
- { url = "https://files.pythonhosted.org/packages/53/48/41a91111590eb7ba68a7c64c03139235a4b8349930abcb87931aa80da796/pydantic_core-2.50.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a44101320cfe99432db74237545a63057dc7a88dfe792cbcad0647f2af56cb81", size = 2480832, upload-time = "2026-10-11T18:31:54.06Z" },
- { url = "https://files.pythonhosted.org/packages/2a/02/4ac93a3d3f0ff412df1a36a9619743336fe6eec68c0df8a914163a3d22b4/pydantic_core-2.50.1-cp310-cp310-win32.whl", hash = "sha256:a4aaaa791bdae1c972a7e81765f4f3571c926b8e0b9b6e47346499fb80079665", size = 2123154, upload-time = "2026-10-11T18:31:55.396Z" },
- { url = "https://files.pythonhosted.org/packages/6d/ba/3a7af3dcd480c4739aa82cd9fedee79a5531a7c553346abcf095bd05f0de/pydantic_core-2.50.1-cp310-cp310-win_amd64.whl", hash = "sha256:2eedf82ee4753cdab8e50044c6bd569577eebc3859b11fecf4eb9223761ff966", size = 2179366, upload-time = "2026-10-11T18:31:56.697Z" },
- { url = "https://files.pythonhosted.org/packages/45/1b/ca81811da3d2aea724fb3c4ba6b83f50ab8f210576306ccd601973142d35/pydantic_core-2.50.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c531166c42ea7bdfecc8c50049581f05dd1993b09cc7c52bb36a14e96deaec7d", size = 2152145, upload-time = "2026-10-11T18:31:58.046Z" },
- { url = "https://files.pythonhosted.org/packages/59/20/938ef7e0541af0a314e2f9d63848dbd1656ba2ebfe5ca0469f7ebee00d5e/pydantic_core-2.50.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6d0c2183008c188e19f4906d426b293bdc4f67ab17df8e180fe16cda208fa71", size = 2045761, upload-time = "2026-10-11T18:31:59.35Z" },
- { url = "https://files.pythonhosted.org/packages/2f/4b/456497fe2affa18f49b226e89bde31e8a204751ee343dd9beaac5438123e/pydantic_core-2.50.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94be440c03fede26969a5ce75468e0e6a9927a1b46d9b679ee8adc1b057b0350", size = 2049557, upload-time = "2026-10-11T18:32:01.121Z" },
- { url = "https://files.pythonhosted.org/packages/a5/9d/cd5dfe13928f55e236dff3a1bee06721d318ca6c955d9ca993b0a5d09f39/pydantic_core-2.50.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:36c426eac0af8d1529ff8467e612b933346caec1fdc0d774f78f67a1a11e16c1", size = 2188153, upload-time = "2026-10-11T18:32:02.442Z" },
- { url = "https://files.pythonhosted.org/packages/fb/8d/d0b25bc484f8d9136ea76ece69e0b2d24ab370cc2e7bbd1b79e61723b763/pydantic_core-2.50.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:028e2f212273d4a39b1ec1e0de8166b1165a65fc0f1111452a9d94fc7c625c63", size = 2388308, upload-time = "2026-10-11T18:32:03.767Z" },
- { url = "https://files.pythonhosted.org/packages/c5/64/6513cac7c9f78492abafa01d57a69de1d246bf3085fd7b0330bf14b025b3/pydantic_core-2.50.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6f0cc1bb9900dc558960894adeb30b0c083366fc1d69b856209fb2ca5c36fe5", size = 2431187, upload-time = "2026-10-11T18:32:05.159Z" },
- { url = "https://files.pythonhosted.org/packages/ed/60/7f2ae89c37e69a3dc0da9ff57b0388c285542c688d05f36767770429de85/pydantic_core-2.50.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8812592c85d0edf423f10eadcef42716d71e8219085ad9e85b775057b7306133", size = 2157623, upload-time = "2026-10-11T18:32:06.616Z" },
- { url = "https://files.pythonhosted.org/packages/87/02/e3a71373b85a7973099b9a229a4302c7792262717e0b4997e41c01f7dd16/pydantic_core-2.50.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:bbce99252ba3167b2b6277f1829d5bf4b43b754524bddf7f944707c3db7d2253", size = 2254539, upload-time = "2026-10-11T18:32:07.968Z" },
- { url = "https://files.pythonhosted.org/packages/3f/d5/5b620aaf40dc631d911565b64d49d3fa8422027c046a7a96dc4e4921ae98/pydantic_core-2.50.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:476f6ed8e43cd1e0b460920e23571700872b284e77331cb30c4faf459cf48a4b", size = 2267344, upload-time = "2026-10-11T18:32:09.399Z" },
- { url = "https://files.pythonhosted.org/packages/e0/48/f46baf4580d8a56ca4782aa65842e16d3cf39736b29fcce8e9c0521b3422/pydantic_core-2.50.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2cbd1b75b09e976ed0d6b6ca297675632ca35df86130088457cdc60ef36970ae", size = 2300877, upload-time = "2026-10-11T18:32:10.903Z" },
- { url = "https://files.pythonhosted.org/packages/2b/9d/e7d28505e6092c1fe075838a4f793ccbe6dd676e44ab16175815ee06cfe0/pydantic_core-2.50.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:5958c72adb417c39b12ac87525ac60b0d73315fcdc59e21f44ee4a5e2512c9ef", size = 2464270, upload-time = "2026-10-11T18:32:12.329Z" },
- { url = "https://files.pythonhosted.org/packages/22/b1/3bf63b82efd93ebc9968f9c12fc46037a28bcb452c88e4688f33e5ff7dca/pydantic_core-2.50.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d8f9e8a6c4ab04b78d61f78627370d834eb004b2869dcb28cfffa647b4ea1980", size = 2478653, upload-time = "2026-10-11T18:32:13.735Z" },
- { url = "https://files.pythonhosted.org/packages/28/17/26d1e4b9c2ba6a92b7dc75bbcb99ef44456b7e3af1bced868403667ea7d3/pydantic_core-2.50.1-cp311-cp311-win32.whl", hash = "sha256:4be846f55c9477f5f3ddde8f2ce941137e16862a56d018ed885d422bb6ae02f2", size = 2120246, upload-time = "2026-10-11T18:32:15.591Z" },
- { url = "https://files.pythonhosted.org/packages/24/65/3e875e7991eaecd4d70686293ff0d8a99aa8f93dc295e6a21c3d702912fb/pydantic_core-2.50.1-cp311-cp311-win_amd64.whl", hash = "sha256:0048b6dddc8ef4b64fccaad878bd143b0c3882ea9936279dc11d613f6b7dd1bc", size = 2176332, upload-time = "2026-10-11T18:32:17.153Z" },
- { url = "https://files.pythonhosted.org/packages/ab/5c/58dbd17c61f209d91c70f549eb7a3bbec7023e89ea12dead091462d6d658/pydantic_core-2.50.1-cp311-cp311-win_arm64.whl", hash = "sha256:6a733778df2f7087ec1100ed0b41533e4f3001976e99570fa34f57c66e7f8e3e", size = 2182471, upload-time = "2026-10-11T18:32:18.49Z" },
- { url = "https://files.pythonhosted.org/packages/ee/94/101b49a6c63f99586755690696eae14532772587d9e39e93f7b8d28a49da/pydantic_core-2.50.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:704075d10b74f2f3c6e15407c696d88701df35fc8953f434a431add0d0074db0", size = 2222428, upload-time = "2026-10-11T18:32:20.228Z" },
- { url = "https://files.pythonhosted.org/packages/db/a3/f8b1b39349c33037c73d7a1b794c9c29d8f07d5bd2b74399c1375a529cc6/pydantic_core-2.50.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e8e1d6ce820aa23317e8209a86bd65a540973c12dc7552b48a4f6c8e9926815e", size = 2025779, upload-time = "2026-10-11T18:32:21.723Z" },
- { url = "https://files.pythonhosted.org/packages/d2/2d/862a7d115305cc621358e51ba13f296e0ffdf4fbd984be9f63bc7862730b/pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c18db21573bd2c6489f9a544b7499f0df2853958c568e5e783536ee1f690af41", size = 2048429, upload-time = "2026-10-11T18:32:23.3Z" },
- { url = "https://files.pythonhosted.org/packages/c0/7d/fed95d18bf2b5abe2d5ab2bbec4184bfc22b6805994a33ccdae8edffd25a/pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb57f304525a5e3c13333b772bf9a473f36326e9c821b2e8e1b2fd36f80ae2c3", size = 2173274, upload-time = "2026-10-11T18:32:24.697Z" },
- { url = "https://files.pythonhosted.org/packages/93/1e/c6f22ceac65dc3d291ff79bea0eb74cbd1de8b128f01fee48af03bcea1e1/pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a27c09d86600f1bf2fe3f37e1ae697faf3143931c09322cd799da94deee923b5", size = 2360403, upload-time = "2026-10-11T18:32:26.281Z" },
- { url = "https://files.pythonhosted.org/packages/2c/97/190b5b6bfe4c72f265feb657913eb30749193927a5ad7b83cfa7866a25a2/pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46b3301d3b5c886f77de7546e47274a5842c622ea2020b8c6524c6b66913b4a6", size = 2439799, upload-time = "2026-10-11T18:32:27.844Z" },
- { url = "https://files.pythonhosted.org/packages/90/24/48cd98388e4ac2c08d0800af36d15413db15e20387f1038123a2c86cb2f6/pydantic_core-2.50.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ba4e9d8210d941c200431a56b2c0400b131865947903937ed3ec5404307d2e", size = 2160157, upload-time = "2026-10-11T18:32:29.553Z" },
- { url = "https://files.pythonhosted.org/packages/00/86/d909133d0b9979ed14c20f110f21124a0e346caf071bd66ac5af3009062b/pydantic_core-2.50.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:e5faeaee74a57d32b3ab3aebad2e348f06d3ba946fc5d28c1728455f00a3d13a", size = 2229523, upload-time = "2026-10-11T18:32:31.027Z" },
- { url = "https://files.pythonhosted.org/packages/90/31/4b43f08131b2098e73a852fd932063385f3103b33839fd9d1abdb89e673d/pydantic_core-2.50.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3cda0e538208e5d722bbf3698b24f19c0a7d05bc8d5f8a7f9b121ea7fa243d9", size = 2242700, upload-time = "2026-10-11T18:32:32.622Z" },
- { url = "https://files.pythonhosted.org/packages/88/77/de39658569dc7a4c293d110a89629f61145e7538f00d1e4280c28debefd3/pydantic_core-2.50.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:57f51b31ff826e2859120cf4737c5a758a48d96f3e97da40ccee1796d58078ff", size = 2278452, upload-time = "2026-10-11T18:32:34.165Z" },
- { url = "https://files.pythonhosted.org/packages/46/bd/1eeb9e4e791e81210ef9096f58080b32ba15b5b13c108d50aaa3f8ce9f73/pydantic_core-2.50.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8daa7ee75245d43ad7d747e5c9ecc1b1d06552f72b14887e9276f787d57375f4", size = 2450098, upload-time = "2026-10-11T18:32:35.547Z" },
- { url = "https://files.pythonhosted.org/packages/1c/e6/b2accfdff17e5a88d6358536702146de00f9339adaa4d5bbe1ee93cb4832/pydantic_core-2.50.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:acbf31f37c53a5ac0c34706c80b4f5107ba20b05fdd3816124bf236ef0c57dd2", size = 2476839, upload-time = "2026-10-11T18:32:37.119Z" },
- { url = "https://files.pythonhosted.org/packages/3b/7e/c8a83eb5d2cd42c2e13507150fb777b1edea5a018adc9cbf2858a1d08285/pydantic_core-2.50.1-cp312-cp312-win32.whl", hash = "sha256:45b11cac094aa25725581d9304eee93c9028516b9ea80dd9e175e13a5a2c840e", size = 2101425, upload-time = "2026-10-11T18:32:38.593Z" },
- { url = "https://files.pythonhosted.org/packages/c5/17/8bbd530b8659e9d963f5b16f6cb8e159f4cd174b4d6304e48969ab12dba8/pydantic_core-2.50.1-cp312-cp312-win_amd64.whl", hash = "sha256:132529c83901437ff642f585216831bf5fd7a91df66829907e155192ead62498", size = 2172522, upload-time = "2026-10-11T18:32:40.112Z" },
- { url = "https://files.pythonhosted.org/packages/af/9f/a79d690b127e4e3cff143d031538ab9aa715f6b2fbeecbd07cd6f2bf9fb7/pydantic_core-2.50.1-cp312-cp312-win_arm64.whl", hash = "sha256:4e834f6a8e4ff772dcc34f58ef5504147a3ea5b0f4eeb13b0f8eb2ca75ac57f1", size = 2161366, upload-time = "2026-10-11T18:32:41.606Z" },
- { url = "https://files.pythonhosted.org/packages/ae/57/0e237d7091d2cd44a35d243b7344227f90440166860469cd036afc23a04d/pydantic_core-2.50.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:d5e062c01286d861fd6a1c4ff6e063547b3e713067f2df033c0ff97ac2ca006b", size = 2222774, upload-time = "2026-10-11T18:32:43.103Z" },
- { url = "https://files.pythonhosted.org/packages/f7/b9/c720e56858d4e1539503297ed37063e0c08e0f3541c41b777f6a800f75fa/pydantic_core-2.50.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0c003c3b7f49debb893d2d85ae099ac5959c9839e2f330fadb1fcdf7a6594482", size = 2024738, upload-time = "2026-10-11T18:32:44.587Z" },
- { url = "https://files.pythonhosted.org/packages/4d/b8/fbfc25875219cc060e613170ff10e850c6d8924beb4910da57c2ee3ba1d2/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:409e0ea40ec30d9158f33574fd758e689f6045a0f2596701828c27816ca9687d", size = 2047049, upload-time = "2026-10-11T18:32:46.164Z" },
- { url = "https://files.pythonhosted.org/packages/d7/43/34210124d504c553688f2f04b48500b131237528ac545b445e0d6d30e0d5/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:131059670f1d2444269b8585cb888963994871932447c08b39ac6a51fcfef658", size = 2173415, upload-time = "2026-10-11T18:32:47.722Z" },
- { url = "https://files.pythonhosted.org/packages/65/cf/6e178e8fdc11da5965bef980983bf46326a42f436871dd51ba0a57f39df1/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dbcbee53bf17196a7f745aa9bf5a9603953a1e365b1f020be3207c676a3e7c4", size = 2361719, upload-time = "2026-10-11T18:32:49.217Z" },
- { url = "https://files.pythonhosted.org/packages/11/14/bd5169d356aa91bf777e28ba0b281c192d286d03c2812c9c9db023a2f00e/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:325c23f3e35cfbf0fe3486fa5f7260d1e45885173002d30a28ca019994124255", size = 2439803, upload-time = "2026-10-11T18:32:51.025Z" },
- { url = "https://files.pythonhosted.org/packages/1c/bc/d79d000e5203ebef39af839f2ce77a777fcad6e08ad26af9a2fcd114ffc8/pydantic_core-2.50.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17e722e156d0444ecaefbe640bdb60928752bf2013e2b7a11cdb099aaae19bec", size = 2158402, upload-time = "2026-10-11T18:32:52.714Z" },
- { url = "https://files.pythonhosted.org/packages/1b/a5/4902cb5fd599422c130bd3124ab31ee8b771199bd56662702eed07d3fec7/pydantic_core-2.50.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:aa8224f10880d9bf1b5993988ba153d42a8b4f3f4f511f93b1f09c93ff613c72", size = 2229755, upload-time = "2026-10-11T18:32:54.126Z" },
- { url = "https://files.pythonhosted.org/packages/1a/f5/c1481f8669f6060d89110c9b1374173fc8767ca276b84f4870bd8acd5e3f/pydantic_core-2.50.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:41bc8237121bd8dc8d888dfd6279fc166ffc88c1f1bf3a8bf00869680533ca4c", size = 2242303, upload-time = "2026-10-11T18:32:55.641Z" },
- { url = "https://files.pythonhosted.org/packages/04/f9/77fc3c7653ba9b6e42049e17e96b25388187d4a7c274ab1cb07f58ac8419/pydantic_core-2.50.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:45c6266d071c241f2a168d45bf8c54344f0effce35e7e6b73afdec11f3687568", size = 2278345, upload-time = "2026-10-11T18:32:57.38Z" },
- { url = "https://files.pythonhosted.org/packages/95/9b/0579c5d12e7f2b16b27e6782427987341fcce07b0725e02ddb0b74add0c4/pydantic_core-2.50.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:1deeacb112d14d3f4fcb16b165f7dbaf76c70ba6e82f37ba042bdab51970a0b8", size = 2449947, upload-time = "2026-10-11T18:32:58.896Z" },
- { url = "https://files.pythonhosted.org/packages/a8/ac/1b677db91eba54cc5922f4de6edc46ba45c2bd712dfdc0130382d158e214/pydantic_core-2.50.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1c96fd793b73d1b92e65570132505498fe7b21eaef73cdf74e67e5dfba7ac9e4", size = 2476889, upload-time = "2026-10-11T18:33:00.665Z" },
- { url = "https://files.pythonhosted.org/packages/4d/2a/3a9f6624ee3ea9ccba5249dde11418bb4c35780a7a92608f8768bd3fea39/pydantic_core-2.50.1-cp313-cp313-win32.whl", hash = "sha256:06ead20d39ffd6f2f6f2a8f8a6de67ff8bb1b4f14a8a30e058502514ee2ac685", size = 2101118, upload-time = "2026-10-11T18:33:02.329Z" },
- { url = "https://files.pythonhosted.org/packages/2d/1f/323f78ddd9d9938aac420c1abb4e8ba799fc8bdab0acc67c0593b837c979/pydantic_core-2.50.1-cp313-cp313-win_amd64.whl", hash = "sha256:7816e98acc08119dc0f340ab167048ecc54126316330c1f0caf7c6756c88e28f", size = 2170840, upload-time = "2026-10-11T18:33:03.919Z" },
- { url = "https://files.pythonhosted.org/packages/cb/09/8497c52a739ae425c3ac2f7f56414cbc711c67d374346174f40fe2062644/pydantic_core-2.50.1-cp313-cp313-win_arm64.whl", hash = "sha256:c17799a62c142d61b8a3c51752a7cbc87fe2ad4ccfab10e628a77b405075c662", size = 2161633, upload-time = "2026-10-11T18:33:05.518Z" },
- { url = "https://files.pythonhosted.org/packages/49/33/28b96e81677153715e3eafb9f26663a80841e859fde282a380359b0d3fa1/pydantic_core-2.50.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1cf41f1ae3fa155cf167a72689ad044bcc1e3c97e064123677149bdfb5dafc4a", size = 2224428, upload-time = "2026-10-11T18:33:07.153Z" },
- { url = "https://files.pythonhosted.org/packages/94/40/15c06410c9b7b8da5805d27b64e09bd3f900e986728106db2689dbc51513/pydantic_core-2.50.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4df197990c15b5a37c5a277d131d9f2c67de6133f2e5dafd80d9bba4b99f46f9", size = 2023740, upload-time = "2026-10-11T18:33:08.763Z" },
- { url = "https://files.pythonhosted.org/packages/a1/4e/5eb629f6efc2a27e421d789dd4bfacbb5f6d09c80c28b13d1e87d73163d5/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0036473f5583e6a60e50b8b21651511564277a3f05cc5dab8cf579f552cd5f6c", size = 2049435, upload-time = "2026-10-11T18:33:10.366Z" },
- { url = "https://files.pythonhosted.org/packages/ba/8e/f195aebec49ad12318876ac2368c197a7939f5d52f8e156d2238ef8a4588/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:992c3514ec891fa7858099183e4d64e6bd5a5d4ff452fae29df22faa77a006bb", size = 2172975, upload-time = "2026-10-11T18:33:12.368Z" },
- { url = "https://files.pythonhosted.org/packages/e0/f5/7ad9fb83010cd5ea0948409db105676ed779c4e709e2d3d89b9fe2558794/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:739dc730e6be3bd5ec2f4ab5cfc7eb047cc45fc1497b3bafec74ff2ed07df597", size = 2364197, upload-time = "2026-10-11T18:33:14.244Z" },
- { url = "https://files.pythonhosted.org/packages/ca/fb/bf0aab3e78301d202b82a0322ec968cd703b11fc0623fde9a28708936f62/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32fad3a91e51b6d2039c572db04a5a873260b399f6bd62c3552671fa7a4a2899", size = 2444259, upload-time = "2026-10-11T18:33:15.79Z" },
- { url = "https://files.pythonhosted.org/packages/45/35/38f6d6564fae57d9b12e5676dfa947e0e7d5c46dbeaa7eb3352261d6d299/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42b54c2c90ad348b5e3a85e03e715d572c1fde357ef104cdfe3b03b697a404ea", size = 2159857, upload-time = "2026-10-11T18:33:17.51Z" },
- { url = "https://files.pythonhosted.org/packages/65/a0/fb0a3ca10f139dcf765b2d312d10cf0f65c57c59993229d00ad12b14ceff/pydantic_core-2.50.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:2df1ff41884de2bc4b307bafd7c40a691094fad2ff8e767e5b45a319257bcf4e", size = 2233954, upload-time = "2026-10-11T18:33:19.591Z" },
- { url = "https://files.pythonhosted.org/packages/8c/6a/e63842252702aa4ec6e6b7178ca85e541a77459076592c23ebe2d9840b33/pydantic_core-2.50.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe90228920fd8ff2be62622b6bb8a2b11acd65046d50c6b130614b5879605a20", size = 2243876, upload-time = "2026-10-11T18:33:21.393Z" },
- { url = "https://files.pythonhosted.org/packages/39/25/5991cf8318b37e0dfab47b87541619a1df8501a793cba0d978846cba37a7/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:844b869f118e22a41a091bdcedda8a71bc1b0f62c38d1a0c3211cece47e1d8fc", size = 2277908, upload-time = "2026-10-11T18:33:23.324Z" },
- { url = "https://files.pythonhosted.org/packages/a7/3e/3ee8baaa6cc25a6961c69168cf9ff0f002d56f4e0d541ad6724c18fb61f3/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:2eb75304506894a281d346220a4f7481a1b8729577c5ed2a05395991966a8396", size = 2449285, upload-time = "2026-10-11T18:33:24.942Z" },
- { url = "https://files.pythonhosted.org/packages/c0/c7/acbec6deac13fe697a80c275a9b6661db62c4d323bac8a47347b1f39c7cd/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6b20a4bffabdad0db2927ac034ae3b8a681b1f7a0182f3e60b479ad2fde21ebb", size = 2479013, upload-time = "2026-10-11T18:33:26.925Z" },
- { url = "https://files.pythonhosted.org/packages/10/08/21a3f237b264389f6219d053ee78fc1b5c2fd402c1b1cb2b6cb8b85f9834/pydantic_core-2.50.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:99ba9bc2b8062ea0c326a990f7f00e6530c23579de66dd246e72c4cafef950a5", size = 1484400, upload-time = "2026-10-11T18:33:28.693Z" },
- { url = "https://files.pythonhosted.org/packages/09/ce/077a6d262d12ef09108377ac0717f029f420d773ace63cafd6143af75568/pydantic_core-2.50.1-cp314-cp314-win32.whl", hash = "sha256:cf356f70551d40374eaffb1aa63f1eb6d2006681cbd7a9faea173ce0f4dd7cd2", size = 2103222, upload-time = "2026-10-11T18:33:30.326Z" },
- { url = "https://files.pythonhosted.org/packages/14/4c/350a2415209c43d670eb71d3c040f332c04a30583d39e311b05c7ac15762/pydantic_core-2.50.1-cp314-cp314-win_amd64.whl", hash = "sha256:d32f3acc081cc3923386d88f422cde8892335e95f034e0104bb4cf9310d9915f", size = 2171586, upload-time = "2026-10-11T18:33:32.139Z" },
- { url = "https://files.pythonhosted.org/packages/bf/92/9bea6ca96580a0902fed366f064f0829e404f41889b34543279a1162b888/pydantic_core-2.50.1-cp314-cp314-win_arm64.whl", hash = "sha256:bed5163e03b98bc1fa2eb05d74c63d9c5c95d8ed6254985481640fbf5e237dea", size = 2155773, upload-time = "2026-10-11T18:33:33.896Z" },
- { url = "https://files.pythonhosted.org/packages/86/8c/f121f073cf32bdd5cba7e6230b1ce0ac845b0cf43e44dd597d95af272db2/pydantic_core-2.50.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:9572c1369e9c9da2d64a7b7992c786d90ff295abc93964cfe3125e4290768070", size = 2234017, upload-time = "2026-10-11T18:33:35.588Z" },
- { url = "https://files.pythonhosted.org/packages/a4/69/2bb2bcd6146cffe98d760a46c42ae71efd5151d9b2f9c9bf6619a3b32083/pydantic_core-2.50.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2005207aafe1231315718bf6ed5d064a7300fb4772754af35ee72fc68159492e", size = 2001016, upload-time = "2026-10-11T18:33:37.57Z" },
- { url = "https://files.pythonhosted.org/packages/20/b3/fbf854c7d07ec114260c26e9e2071a4381740f9ae09641dbfcbdf2a18c45/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f6047f62a6c5ae08d0a6afb035667aa2d97c3d20d69762e034c5ea144d92a5", size = 2023015, upload-time = "2026-10-11T18:33:39.433Z" },
- { url = "https://files.pythonhosted.org/packages/65/20/6de55b2f92cdb614b745c6e9ced639fc4fd7e1e77604825a88bdece6fcbd/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ef800dd7d85bcdadf4c3076e4c94e43939493558a3b69a1ea830c706d4617bb", size = 2192543, upload-time = "2026-10-11T18:33:41.381Z" },
- { url = "https://files.pythonhosted.org/packages/0c/d9/19e91c94bd5c405945ce2f15526808aea37e162c253160de8ed7bf70b406/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0135bcdcaa0f23573f286e4cb5e0fd2962700964ed13df085b85f2b97aeab9e", size = 2388704, upload-time = "2026-10-11T18:33:43.167Z" },
- { url = "https://files.pythonhosted.org/packages/b6/bc/2e24c8415eae1a25ee5a5484946a917123bad2e9d01689a759236928175a/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b3a6f334c6a2345ca15318ff894502a90012536404b37c844a976c76c846e0b", size = 2464118, upload-time = "2026-10-11T18:33:44.856Z" },
- { url = "https://files.pythonhosted.org/packages/bd/1f/945b8053cb061c64e102bcaf7bfb9ed740c0bd4349f9bb978a7d4ddff4ab/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06e01fbbfdb9be777b316a71b6c49efaf4a08b615d0a98d678cda3023f79d019", size = 2150142, upload-time = "2026-10-11T18:33:46.661Z" },
- { url = "https://files.pythonhosted.org/packages/2b/78/96a3e50bf0d64aaae781107eb9335b7529d05a85793ed6e7241c4cd1d931/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a29a061fec0b4e2d714f277e70a3a18125ecff803f2fea6eade2f2e53711d112", size = 2245577, upload-time = "2026-10-11T18:33:48.574Z" },
- { url = "https://files.pythonhosted.org/packages/7a/5d/a6038a0322232758a6ebfa709f4ca14a60bf5b93940cd0b345056a557da4/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f5187624823423e1d1b82b1072ac41dc837389e18d3d0572cc19bbee46cd550a", size = 2263728, upload-time = "2026-10-11T18:33:50.527Z" },
- { url = "https://files.pythonhosted.org/packages/0d/4b/76ded3333a457a9344c4f2d63f2d82d0101654b05178fa4ddfe7d3627674/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3e46a9eb0a0901dd6275e6b06ac3a464885ef350ec4121fe486869de8053e4bb", size = 2293630, upload-time = "2026-10-11T18:33:52.362Z" },
- { url = "https://files.pythonhosted.org/packages/a9/00/9eca378335c9c1b72bc779bf6e4c2a82ce784f14ec48a0e87102c9870e05/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:756d669f04e62ec4148ecfe22be6a4484d9b1181a6ef32e205ebfd200540858b", size = 2468048, upload-time = "2026-10-11T18:33:54.118Z" },
- { url = "https://files.pythonhosted.org/packages/35/ca/e3832e9cf93651251de43c8c5c029ed680a3c1a0a1b17ee26c81bed08cbd/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:c516cc5367ca3448995d42cb994bf3f4c9002d2a7c22eac9622551269ad1b807", size = 2512159, upload-time = "2026-10-11T18:33:55.99Z" },
- { url = "https://files.pythonhosted.org/packages/4b/f2/773469b5a10a39116a2cb17edaf6d722f017dd8da07161b371465311c542/pydantic_core-2.50.1-cp314-cp314t-win32.whl", hash = "sha256:9d1bed94af6a63835461f3cf7502058eb166c58c4778e11d0f433cfb1bd69e19", size = 2098406, upload-time = "2026-10-11T18:33:58.043Z" },
- { url = "https://files.pythonhosted.org/packages/ee/42/0bb74f8f25204b259b11ab7c12dc7f180893b6bd706118b385147fb6efd5/pydantic_core-2.50.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c8dce1f1e0e5358b682a6ad3fa5e31b31d4560997b8e61417e9217c8d60f8a0c", size = 2148481, upload-time = "2026-10-11T18:33:59.913Z" },
- { url = "https://files.pythonhosted.org/packages/47/0d/d801646c9679a4e630e15cf521d4108b93854b42a6e6e02391bd4c6b1095/pydantic_core-2.50.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ceff0acc940be2715bd6ad17b24c0e5304abf44f6efd0f81ee8499e640f9dc86", size = 2174972, upload-time = "2026-10-11T18:34:01.875Z" },
- { url = "https://files.pythonhosted.org/packages/b2/84/23984b763d8862a02a13d27a44b6e8169428fd85ecfed88f54c29108604d/pydantic_core-2.50.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:8a6791afa2245e6c6b180122d105941644f5bd410bb18623b408808cc41a3102", size = 2224599, upload-time = "2026-10-11T18:34:04.016Z" },
- { url = "https://files.pythonhosted.org/packages/4d/90/a63cf8586abc1d1a3f6d9b18f0224789ebf003851f092ebda3c7c863fd32/pydantic_core-2.50.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:84f34323a61a365b4e9295de6028474754829aaddd59c7bf1a040e7487ef8f3c", size = 2024191, upload-time = "2026-10-11T18:34:05.901Z" },
- { url = "https://files.pythonhosted.org/packages/a6/6a/f34bff9808ffb4907fbf5f5040457d253cacf2da7fce9efbc99ca1b1a44d/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23edad659e8dbd8ca7e4e877fe6c81573abbdf215bd25a68b53e1272f58b80c7", size = 2048252, upload-time = "2026-10-11T18:34:07.757Z" },
- { url = "https://files.pythonhosted.org/packages/b2/54/13f419bf1eb59852003818e25935aaf175687d978f4c4bca70e08fe40a3b/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a5fce22f1e87d181e924e12da7d81cfe031fb3881a5ddf26ad28f141756ca43", size = 2173376, upload-time = "2026-10-11T18:34:09.592Z" },
- { url = "https://files.pythonhosted.org/packages/6d/6c/b5a34d24cd0c81669d8f8339d74e6815abcf2f8fb48ab4b49b84c09be1d5/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c73622ef819328873b53109ee4f77ceb598bffedd02daf916102be3228866b78", size = 2364559, upload-time = "2026-10-11T18:34:11.534Z" },
- { url = "https://files.pythonhosted.org/packages/eb/8d/d64d6216a8df365082665927ff923f183056f9049fee08e9777c9ac05296/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce8c25ca38cc0e3d7753ba180808de2c0c8cb24eae0df64491e40921454e9831", size = 2444697, upload-time = "2026-10-11T18:34:13.535Z" },
- { url = "https://files.pythonhosted.org/packages/b8/0d/1b1149f60a00ea21ba5f70e28acbd40feb4598af414f80f53c921fac07c9/pydantic_core-2.50.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7689580e72a642ab5ec64d5f55b2e33636fa43b4ebe63c0c2c965ef307c7d1aa", size = 2159961, upload-time = "2026-10-11T18:34:15.56Z" },
- { url = "https://files.pythonhosted.org/packages/1e/35/f236549299dcc78e71e945495d7ec67e78d20844d0999c60601685003031/pydantic_core-2.50.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:d5c0e32fdbce7f1e8ef4d11f655694bf5f4175c757a9f1dc2be09b8864e5bcf5", size = 2234581, upload-time = "2026-10-11T18:34:17.502Z" },
- { url = "https://files.pythonhosted.org/packages/6a/85/26901a490522b7f75ef9bb9a7afb73e5bb550f0e1cb5b99a0069183e2eb9/pydantic_core-2.50.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40f523349960fa30f3ea51404308ff50f9997a90df639590f47a057c1f32b415", size = 2244090, upload-time = "2026-10-11T18:34:19.402Z" },
- { url = "https://files.pythonhosted.org/packages/ca/2c/481bcfc70ceeb77a778ca6e5b705fe592cb64735c108157f81b7dd9c280e/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:d4193206b6587047437f6f11d7e776df23e1c1e23af2a54d9347275614791e10", size = 2277948, upload-time = "2026-10-11T18:34:21.317Z" },
- { url = "https://files.pythonhosted.org/packages/24/eb/f1e09333faa7ba447cde967310f758430cc7c5e987bdab5228816c70030d/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_armv7l.whl", hash = "sha256:84bc765b282a9d5b7fe0348b8648904f25a6a04b2139da52b1dd30c8ac3a2c8f", size = 2449688, upload-time = "2026-10-11T18:34:23.321Z" },
- { url = "https://files.pythonhosted.org/packages/d1/b3/036bde636db8f76d92996e81aefc75678ab5cec4a07eea1ad0c72a893fc3/pydantic_core-2.50.1-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:ed1e728b39a383c81035b2459cfcb35d99dfb01f7d6ebe3a913bc1cc5b81e459", size = 2479286, upload-time = "2026-10-11T18:34:25.214Z" },
- { url = "https://files.pythonhosted.org/packages/d0/a3/07f018294ee18d144afeb6df1a47d9e92960f014be45c5ed13519fa5af95/pydantic_core-2.50.1-cp315-cp315-win32.whl", hash = "sha256:bc94f474417604bd383d2cd445d071b07dd55fedceed3ce33407bf1fcc107290", size = 2103683, upload-time = "2026-10-11T18:34:27.42Z" },
- { url = "https://files.pythonhosted.org/packages/5f/98/f9bd7e1f9b6709f155acb9ef826d9c3884fe925f811fd8e55b9b52280bad/pydantic_core-2.50.1-cp315-cp315-win_amd64.whl", hash = "sha256:983a662de2571cb2502fc8ff47b6770b03d025d2eb314c92f77b3f07c74720ed", size = 2171681, upload-time = "2026-10-11T18:34:29.508Z" },
- { url = "https://files.pythonhosted.org/packages/10/87/4bb3e1e7f385c076ab5af4d6dd0571b22042cd8207eb810d5b9fef15ae31/pydantic_core-2.50.1-cp315-cp315-win_arm64.whl", hash = "sha256:94845ff54dc5193f228cab81b2662a04bfbb892e95bdc15edf7399000ce57d54", size = 2156251, upload-time = "2026-10-11T18:34:31.455Z" },
- { url = "https://files.pythonhosted.org/packages/47/47/83643225b08f2aef6c8cc4bbe6e3f79c5e139c4364a6e450d0f399d87774/pydantic_core-2.50.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:4a53d13cdfbedbfa87f08b83c1a0a5efcc767d785a4b41934fa9cb672670493a", size = 2234363, upload-time = "2026-10-11T18:34:33.65Z" },
- { url = "https://files.pythonhosted.org/packages/5e/66/127ca649ba2f2462039dc1e694c6c01e00a3689f023a617364d3507e6d97/pydantic_core-2.50.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:efbecf43d321f7b9281441f1f213f7c21c66988b0e06c2730ba13ed47a46bb08", size = 2000972, upload-time = "2026-10-11T18:34:36.037Z" },
- { url = "https://files.pythonhosted.org/packages/5e/4f/e421e0a5d653b1203b090b2e48745976988d7338a647940704b5b9c2b399/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc1f08f68dac9f9e83845a8039880aba2ab553eb9b2259c3243a313182c253fe", size = 2022925, upload-time = "2026-10-11T18:34:37.972Z" },
- { url = "https://files.pythonhosted.org/packages/d5/4e/ea5568e2491e1a71100f15ae8c2d01ef52db42a184a2d4e716bc79e5eb8f/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5dfe41f232befddb9c4377f6cfc702b51595e2d78ed082672adf8758d2c4619f", size = 2192867, upload-time = "2026-10-11T18:34:39.921Z" },
- { url = "https://files.pythonhosted.org/packages/ae/5e/3b8c3a35acbe219909ada5defad5d7d9fed845fb2b37bd5ec518f453c119/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adc06d218a1cadfd2ec4628424d7d79ce4eba69c2965e7e7b55106f0da5208c8", size = 2388687, upload-time = "2026-10-11T18:34:42.184Z" },
- { url = "https://files.pythonhosted.org/packages/b6/aa/7889b4e515f91a2e8c0ae6b5081fec0feb30c4398d1434e14793c60f173a/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cf91809d0721ab81592ba67bea7694821679c10b1a2e3c3460082b286c1918a", size = 2464415, upload-time = "2026-10-11T18:34:44.384Z" },
- { url = "https://files.pythonhosted.org/packages/08/78/93449e628eb8a6fdcce3eff9043081179d1bc7ce6f1bff32dc5006f41e00/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23923ab9292c40da026330b1ecf4dc2618c8e86e0422e5d1fbf50d94d64ca4f8", size = 2150823, upload-time = "2026-10-11T18:34:46.392Z" },
- { url = "https://files.pythonhosted.org/packages/f4/f1/72c5bc129fceb0d00f05dc1e67f518c1728de1928c55f81fc13d7690de39/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:f3377c8c2b3ce898423c5e5dd94c7982e30aa7717a7e6ab2470b9de364963709", size = 2245806, upload-time = "2026-10-11T18:34:48.805Z" },
- { url = "https://files.pythonhosted.org/packages/28/2a/922a0e78f3aa6ab837b59f88190233fb8dad546c17995fde9bb3ed3b9b49/pydantic_core-2.50.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:455a773617b5913bf5c20d0692e5787b119e52c4d40ea644ca31f5758fd31be2", size = 2264160, upload-time = "2026-10-11T18:34:50.876Z" },
- { url = "https://files.pythonhosted.org/packages/52/a8/0f1449e3e1b20941c9372faa18e7b6a092e30cdfa841902473f7989532ac/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_aarch64.whl", hash = "sha256:1a9006395dece0e32e704c315eff8a00bede494f6108546cfc5539c89fef4f9a", size = 2294031, upload-time = "2026-10-11T18:34:52.876Z" },
- { url = "https://files.pythonhosted.org/packages/d1/d0/1031f492857de70355fb16524bbb03efce5fd34c93ed4a1ec60be07e0d4b/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_armv7l.whl", hash = "sha256:d2d82aa62521c55ddfb000ae70f88cdd8de974078f6024e821dfe5addd0c818f", size = 2468423, upload-time = "2026-10-11T18:34:54.995Z" },
- { url = "https://files.pythonhosted.org/packages/46/52/269ffffa645b8e47906395a39cf9db1151ae9fa4bcd7f47b960bc31baf37/pydantic_core-2.50.1-cp315-cp315t-musllinux_1_1_x86_64.whl", hash = "sha256:009634b83993777ddcd69cad0ffcace43dabde692109528e35f0fde91e386a8b", size = 2512598, upload-time = "2026-10-11T18:34:57.149Z" },
- { url = "https://files.pythonhosted.org/packages/31/5c/e47e28281f20326ff6f3c31d626f0a83e615d2d94ba41bb0ad6ec237184a/pydantic_core-2.50.1-cp315-cp315t-win32.whl", hash = "sha256:3fde4fdc6487a58d944ca87cf5adc95d5f266e872c19599f5f4c0a8a1b1f9f9f", size = 2098535, upload-time = "2026-10-11T18:34:59.313Z" },
- { url = "https://files.pythonhosted.org/packages/03/ad/759e181e69c1b472c60b2049e5f61d5da1deefdd5ce1df4bb2ebcf771f25/pydantic_core-2.50.1-cp315-cp315t-win_amd64.whl", hash = "sha256:1c8632d4ac04e6f91128fca584b3a8a507d81604c24eeaaad00d4be42765c32b", size = 2148645, upload-time = "2026-10-11T18:35:01.571Z" },
- { url = "https://files.pythonhosted.org/packages/6d/56/8a702c27e5be9f47e5f19d8669227424290e4c024e7c370279cbaf244b4e/pydantic_core-2.50.1-cp315-cp315t-win_arm64.whl", hash = "sha256:c3ede305158e75510be50869b319550ab072008c13d64d4ab1e094fb286b6f44", size = 2175297, upload-time = "2026-10-11T18:35:04.079Z" },
- { url = "https://files.pythonhosted.org/packages/e7/73/a23a327237d9bb985298ce47702e196f995ce850224c12c70886bd66bb5c/pydantic_core-2.50.1-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:062e891facce5ca296a1c37098e5e466780457f86413894b399f0cf22934f769", size = 2162560, upload-time = "2026-10-11T18:35:06.274Z" },
- { url = "https://files.pythonhosted.org/packages/df/b2/33b37b5e82408f402a5e82649a43e7c95cd5dab7beaeef2c35cedbfd18f2/pydantic_core-2.50.1-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:49c2cbb2397fe4d0987e84606e691af6cb87bc0ee1bd3e7b737f7e10b4c142f9", size = 2075250, upload-time = "2026-10-11T18:35:08.455Z" },
- { url = "https://files.pythonhosted.org/packages/04/62/c7d5b9249466ccab90f3ce4790c2399dbfb8172ae9e74a7753dd295fe65a/pydantic_core-2.50.1-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e4472072de0137ee0d8e72d6620e85939c271d2f90f6bbb4b15c24638b79f92", size = 2107891, upload-time = "2026-10-11T18:35:10.918Z" },
- { url = "https://files.pythonhosted.org/packages/d4/7e/28d29bc1657195933147f772dee0558d9e0f045bbc11db7f992f40c37bb5/pydantic_core-2.50.1-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ed4f3cef55164b026fefb41341b7754cc6b624c75dfe7142d2ecceb5ad21c87", size = 2268026, upload-time = "2026-10-11T18:35:13.336Z" },
- { url = "https://files.pythonhosted.org/packages/a5/09/1bcf160f3cd6333e2a76982fae5963e5032de0bcaaba38464223dc266bfe/pydantic_core-2.50.1-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:76e2e83fa6ec8cdc972d438dafc2522b3a47bee4ec0ae668b29cfb1977ab5242", size = 2217070, upload-time = "2026-10-11T18:35:15.688Z" },
- { url = "https://files.pythonhosted.org/packages/3d/81/f02de95eec7794dfc7a5bc69a834f0e834b31b3009014ce6de903855f775/pydantic_core-2.50.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:a51eee75939cf811ac09b278745a6cee7dc873ccfbc8b9af3cc88fe4b7ce25b5", size = 2039027, upload-time = "2026-10-11T18:35:18.07Z" },
- { url = "https://files.pythonhosted.org/packages/4d/16/227746ed3771d9bf2304568b55134849ece34e13b38b1d76de763015c77f/pydantic_core-2.50.1-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae28183297fb0d2b8dc46a1f01d51f5e45825fc5afe76a835a6cb7fb34821295", size = 2109009, upload-time = "2026-10-11T18:35:20.218Z" },
- { url = "https://files.pythonhosted.org/packages/e5/b9/7664d1592a0e5a74903f357cd5b28ceb7336f3473256c5a4e8c8432f1881/pydantic_core-2.50.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88e492e8b9d0312e7dc13667c30222abf284dc3b79b5302b3607b41a5784ce61", size = 2269883, upload-time = "2026-10-11T18:35:22.48Z" },
- { url = "https://files.pythonhosted.org/packages/bd/35/002a378302ff91d2a7f149bc8d22363faf581ba40ac39d0123444315a32d/pydantic_core-2.50.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7456d699b13954e9c0164dcb267250a10ae0dfb03e6e26d6796ab0d46e189c84", size = 2154076, upload-time = "2026-10-11T18:35:24.743Z" },
- { url = "https://files.pythonhosted.org/packages/4b/5c/7a034330082c00f3c9795052bd9b161aaaf6e1951e897ac00a094823ef7e/pydantic_core-2.50.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0d955195bbbe489ad343fcc956eacea9357b79cb22192c66cacdefcbc14b32f", size = 2085850, upload-time = "2026-10-11T18:35:27.009Z" },
- { url = "https://files.pythonhosted.org/packages/57/fa/d5b44a7d3d4eceed6ef8713a0003af56fefde6ec75915513bcdd5d730cd9/pydantic_core-2.50.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2c634642694e6a0dad2ab1d375589fa671fd442edd5caf7d9737b8f6ca22906", size = 2259754, upload-time = "2026-10-11T18:35:29.763Z" },
- { url = "https://files.pythonhosted.org/packages/b6/88/e01dd37301710bd8b3b2daf34ec9e2a6e83a333130831c0d468b0292a48c/pydantic_core-2.50.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ee6db2fbed51a7991302e8fac498cd67e336246026d0dfa84cf5166ce1412760", size = 2268712, upload-time = "2026-10-11T18:35:32.654Z" },
- { url = "https://files.pythonhosted.org/packages/3e/43/5bb80d4a4d2b2d84612a30205662cc521a20d209c94abc079ea48b4e0df2/pydantic_core-2.50.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:79490e33c4c0fcb933bbbcfc3a62184d8803b99f535863dfbb925e1bcb6945ad", size = 2305049, upload-time = "2026-10-11T18:35:35.392Z" },
- { url = "https://files.pythonhosted.org/packages/e8/7b/e3ef76b269cfe4a5bd3bdcf5d7124cf05e5a8909d443b91639307116bdae/pydantic_core-2.50.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:48569b0ade9edfbe065cad1d700175546592aebbb42f02adcebcc26e75b896fe", size = 2466001, upload-time = "2026-10-11T18:35:37.791Z" },
- { url = "https://files.pythonhosted.org/packages/8f/7a/ea0947aadc1d9f72d4be00d2e19939611ec17363bcb5e6eb9ec7d8221c34/pydantic_core-2.50.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5f3cae32fc46121f787cb2486de9cf95a8bf72aec5cc78f64c606fa1735a6ef5", size = 2481777, upload-time = "2026-10-11T18:35:40.287Z" },
- { url = "https://files.pythonhosted.org/packages/24/f3/d15bc0b1fb0c4f1e07b3326ffb7489f7ae70da2b56a0884d22641b0eb47c/pydantic_core-2.50.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7f476456ac2bb0d937f75191494a09c83a30765fea4f70f3b404942fe25f6cdf", size = 2336787, upload-time = "2026-10-11T18:35:42.558Z" },
+sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" },
+ { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" },
+ { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" },
+ { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" },
+ { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" },
+ { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" },
+ { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" },
+ { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" },
+ { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" },
+ { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" },
+ { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" },
+ { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" },
+ { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" },
+ { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" },
+ { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" },
+ { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" },
+ { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" },
+ { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" },
+ { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" },
+ { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" },
+ { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" },
+ { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" },
+ { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" },
+ { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" },
+ { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" },
+ { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" },
+ { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" },
+ { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" },
+ { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" },
+ { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" },
+ { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" },
+ { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" },
+ { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" },
+ { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
+ { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" },
+ { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" },
+ { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" },
+ { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" },
+ { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" },
+ { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" },
+ { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" },
+ { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" },
+ { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" },
]
[[package]]
From eecc73947bab5a5185d2180e102e80dec84ecb77 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 18:10:59 +0530
Subject: [PATCH 27/41] Type the custom modules against the generated API
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
examples/stream_events.py | 4 +---
pyproject.toml | 10 ++++++++++
src/opencode/__init__.py | 5 +++--
src/opencode/_errors.py | 6 +++++-
src/opencode/_events.py | 4 ++--
src/opencode/_pty.py | 19 ++++++++++---------
src/opencode/_rpc.py | 20 ++++++++++----------
src/opencode/lib/_client.py | 4 ++--
src/opencode/lib/_resources.py | 4 ++++
src/opencode/lib/_session.py | 21 ++++++++++-----------
10 files changed, 57 insertions(+), 40 deletions(-)
diff --git a/examples/stream_events.py b/examples/stream_events.py
index 1d336f6..1438a0b 100644
--- a/examples/stream_events.py
+++ b/examples/stream_events.py
@@ -8,7 +8,7 @@
import asyncio
from opencode import AsyncOpencode
-from opencode.types import UnknownEvent, SessionTextDeltaEvent
+from opencode.types import SessionTextDeltaEvent
async def main() -> None:
@@ -16,8 +16,6 @@ async def main() -> None:
async for event in events:
if isinstance(event, SessionTextDeltaEvent):
print(event.data, flush=True)
- elif isinstance(event, UnknownEvent):
- print(f"(new event type from a newer server: {event.type})")
else:
print(event.type)
diff --git a/pyproject.toml b/pyproject.toml
index 8fc1f7e..87021ab 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -139,6 +139,16 @@ reportOverlappingOverload = false
reportImportCycles = false
reportPrivateUsage = false
+[[tool.pyright.executionEnvironments]]
+root = "tests/custom"
+# respx ships without complete type information, and autouse fixtures are never referenced.
+reportUnknownMemberType = false
+reportUnknownVariableType = false
+reportUnknownArgumentType = false
+reportUnknownLambdaType = false
+reportUnknownParameterType = false
+reportUnusedFunction = false
+
[tool.mypy]
pretty = true
show_error_codes = true
diff --git a/src/opencode/__init__.py b/src/opencode/__init__.py
index bc89240..8ba5cac 100644
--- a/src/opencode/__init__.py
+++ b/src/opencode/__init__.py
@@ -137,8 +137,9 @@
from .lib._client import Opencode as Opencode, AsyncOpencode as AsyncOpencode # noqa: E402
from ._base_client import AsyncPaginator # noqa: E402
-Client = Opencode
-AsyncClient = AsyncOpencode
+# The aliases name the clients with this SDK's features, not the generated base classes.
+Client = Opencode # type: ignore[misc]
+AsyncClient = AsyncOpencode # type: ignore[misc]
API_VERSION = "2.1.2"
"""The OpenCode server version this client was generated from."""
diff --git a/src/opencode/_errors.py b/src/opencode/_errors.py
index 66a4756..814e313 100644
--- a/src/opencode/_errors.py
+++ b/src/opencode/_errors.py
@@ -368,9 +368,13 @@ class WorktreeError(TaggedError, BadRequestError):
}
+def _tag_of(body: object) -> object:
+ return cast("dict[str, Any]", body).get("_tag") if isinstance(body, dict) else None
+
+
def tagged_status_error(err_msg: str, *, body: object, response: httpx.Response) -> APIStatusError | None:
"""The error for a ``_tag`` this client knows, or for 413, 501 and 503; ``None`` otherwise."""
- tag: object = cast("dict[str, Any]", body).get("_tag") if isinstance(body, dict) else None
+ tag = _tag_of(body)
cls = TAGGED_ERRORS.get(tag) if isinstance(tag, str) else None
cls = cls or _BY_STATUS.get(response.status_code)
return None if cls is None else cls(err_msg, response=response, body=body)
diff --git a/src/opencode/_events.py b/src/opencode/_events.py
index 915a567..d8032d0 100644
--- a/src/opencode/_events.py
+++ b/src/opencode/_events.py
@@ -368,7 +368,7 @@ def _read(self, connection: _SyncConnection) -> None:
finally:
connection.stream = None
# End the iteration first, so the response's byte stream is closed with it.
- stream._iterator.close() # pyright: ignore[reportPrivateUsage, reportAttributeAccessIssue]
+ stream._iterator.close() # type: ignore[attr-defined] # Stream keeps its generator private
stream.close()
@@ -667,7 +667,7 @@ async def _read(self, connection: _AsyncConnection) -> None:
finally:
with anyio.CancelScope(shield=True):
# End the iteration first, so the response's byte stream is closed with it.
- await stream._iterator.aclose() # pyright: ignore[reportPrivateUsage, reportAttributeAccessIssue]
+ await stream._iterator.aclose() # type: ignore[attr-defined] # Stream keeps its generator private
await stream.close()
diff --git a/src/opencode/_pty.py b/src/opencode/_pty.py
index 38120c4..522c689 100644
--- a/src/opencode/_pty.py
+++ b/src/opencode/_pty.py
@@ -32,7 +32,7 @@
from urllib.parse import urlencode
from typing_extensions import Self
-from ._types import NOT_GIVEN, NotGiven
+from ._types import NOT_GIVEN, Omit, omit
from ._resource import SyncAPIResource, AsyncAPIResource
from ._exceptions import OpencodeError
from .lib._support import encode_query, default_location, encode_path_segment
@@ -46,6 +46,7 @@
from websockets.asyncio.client import ClientConnection as AsyncWebSocket
from .types import PtyID
+ from .lib._client import Opencode, AsyncOpencode
from .lib._support import Location
__all__ = [
@@ -93,12 +94,12 @@ def _ws_url(base_url: httpx.URL, path: str, query: Mapping[str, object]) -> str:
return str(url.copy_with(scheme=scheme, query=urlencode(encode_query(query)).encode("ascii")))
-def _headers(username: str, password: str | None, extra: Mapping[str, str] | None) -> dict[str, str]:
+def _headers(username: str | None, password: str | None, extra: Mapping[str, str] | None) -> dict[str, str]:
import base64
headers: dict[str, str] = {}
if password is not None:
- headers["Authorization"] = "Basic " + base64.b64encode(f"{username}:{password}".encode()).decode("ascii")
+ headers["Authorization"] = "Basic " + base64.b64encode(f"{username or ''}:{password}".encode()).decode("ascii")
headers.update(extra or {})
return headers
@@ -428,7 +429,7 @@ def attach(
self,
pty_id: PtyID,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Location | Omit | None = omit,
cursor: int | None = None,
open_timeout: float | None = 10,
extra_headers: Mapping[str, str] | None = None,
@@ -449,7 +450,7 @@ def attach(
``GET /api/pty/:ptyID/connect`` (WebSocket)
"""
_require_websockets()
- client = self._client
+ client = cast("Opencode", self._client)
token = client.pty.connect.token(pty_id, location=location, x_opencode_ticket="1")
url = _ws_url(
client.base_url,
@@ -471,7 +472,7 @@ async def attach(
self,
pty_id: PtyID,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Location | Omit | None = omit,
cursor: int | None = None,
open_timeout: float | None = 10,
extra_headers: Mapping[str, str] | None = None,
@@ -481,7 +482,7 @@ async def attach(
``GET /api/pty/:ptyID/connect`` (WebSocket)
"""
_require_websockets()
- client = self._client
+ client = cast("AsyncOpencode", self._client)
token = await client.pty.connect.token(pty_id, location=location, x_opencode_ticket="1")
url = _ws_url(
client.base_url,
@@ -529,7 +530,7 @@ def attach(
``GET /api/experimental/persistent-pty/:ptyID/connect`` (WebSocket)
"""
_require_websockets()
- client = self._client
+ client = cast("Opencode", self._client)
token = client.experimental.persistent_pty.connect_token(pty_id, x_opencode_ticket="1")
url = _ws_url(
client.base_url,
@@ -564,7 +565,7 @@ async def attach(
``GET /api/experimental/persistent-pty/:ptyID/connect`` (WebSocket)
"""
_require_websockets()
- client = self._client
+ client = cast("AsyncOpencode", self._client)
token = await client.experimental.persistent_pty.connect_token(pty_id, x_opencode_ticket="1")
url = _ws_url(
client.base_url,
diff --git a/src/opencode/_rpc.py b/src/opencode/_rpc.py
index 2173009..8e64a72 100644
--- a/src/opencode/_rpc.py
+++ b/src/opencode/_rpc.py
@@ -39,7 +39,7 @@ class Counter(RpcDefinition, id="counter"):
import pydantic
-from ._types import NOT_GIVEN, NotGiven
+from ._types import NOT_GIVEN, Omit, NotGiven, omit
from ._errors import RpcError, RpcInternalError
from ._exceptions import OpencodeError
from .lib._support import type_adapter, validation_summary
@@ -146,7 +146,7 @@ def error(self, rpc_id: str, exc: RpcError | RpcInternalError) -> RpcCallError:
except pydantic.ValidationError:
data = body.data
cls = RpcCallInternalError if isinstance(exc, RpcInternalError) else RpcCallError
- return cls(rpc_id, self.name, body.type, body.message, data)
+ return cls(rpc_id, self.name, body.type, body.message or "", data)
class RpcEventDefinition(Generic[_T]):
@@ -281,7 +281,7 @@ def call(
method: RpcMethod[None, _O],
input: None = None,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Location | Omit | None = omit,
extra_headers: Headers | None = None,
timeout: Timeout | NotGiven = NOT_GIVEN,
) -> _O: ...
@@ -291,7 +291,7 @@ def call(
method: RpcMethod[_I, _O],
input: _I,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Location | Omit | None = omit,
extra_headers: Headers | None = None,
timeout: Timeout | NotGiven = NOT_GIVEN,
) -> _O: ...
@@ -300,7 +300,7 @@ def call(
method: RpcMethod[Any, Any],
input: Any = None,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Location | Omit | None = omit,
extra_headers: Headers | None = None,
timeout: Timeout | NotGiven = NOT_GIVEN,
) -> Any:
@@ -354,7 +354,7 @@ async def call(
method: RpcMethod[None, _O],
input: None = None,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Location | Omit | None = omit,
extra_headers: Headers | None = None,
timeout: Timeout | NotGiven = NOT_GIVEN,
) -> _O: ...
@@ -364,7 +364,7 @@ async def call(
method: RpcMethod[_I, _O],
input: _I,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Location | Omit | None = omit,
extra_headers: Headers | None = None,
timeout: Timeout | NotGiven = NOT_GIVEN,
) -> _O: ...
@@ -373,7 +373,7 @@ async def call(
method: RpcMethod[Any, Any],
input: Any = None,
*,
- location: Location | NotGiven | None = NOT_GIVEN,
+ location: Location | Omit | None = omit,
extra_headers: Headers | None = None,
timeout: Timeout | NotGiven = NOT_GIVEN,
) -> Any:
@@ -479,7 +479,7 @@ class TypedRpcResource(RpcResource):
def __call__(self, definition: type[_D]) -> RpcClient[_D]:
"""Binds a plugin RPC declaration to this client."""
- return RpcClient(self._client, definition)
+ return RpcClient(cast("Opencode", self._client), definition)
class AsyncTypedRpcResource(AsyncRpcResource):
@@ -487,4 +487,4 @@ class AsyncTypedRpcResource(AsyncRpcResource):
def __call__(self, definition: type[_D]) -> AsyncRpcClient[_D]:
"""Binds a plugin RPC declaration to this client."""
- return AsyncRpcClient(self._client, definition)
+ return AsyncRpcClient(cast("AsyncOpencode", self._client), definition)
diff --git a/src/opencode/lib/_client.py b/src/opencode/lib/_client.py
index e3b065a..58affc8 100644
--- a/src/opencode/lib/_client.py
+++ b/src/opencode/lib/_client.py
@@ -16,7 +16,7 @@
from __future__ import annotations
import os
-from typing import TYPE_CHECKING, Any, cast
+from typing import TYPE_CHECKING, Any
from typing_extensions import Self, override
import httpx
@@ -59,7 +59,7 @@ def _with_location(options: FinalRequestOptions, directory: str | None) -> Final
params = options.params
if not isinstance(params, dict):
return options
- query = cast("dict[str, object]", params)
+ query: dict[str, object] = params
if "cursor" in query and "order" in query:
query = {key: value for key, value in query.items() if key != "order"}
if directory is not None and "location" not in query:
diff --git a/src/opencode/lib/_resources.py b/src/opencode/lib/_resources.py
index 2c04fc3..039a848 100644
--- a/src/opencode/lib/_resources.py
+++ b/src/opencode/lib/_resources.py
@@ -7,6 +7,8 @@
from __future__ import annotations
+from typing_extensions import override
+
from .._pty import PtyAttachMixin, AsyncPtyAttachMixin, PersistentPtyAttachMixin, AsyncPersistentPtyAttachMixin
from .._compat import cached_property
from ._session import SessionHelpers, AsyncSessionHelpers
@@ -55,6 +57,7 @@ class Experimental(ExperimentalResource):
"""``client.experimental``, whose ``persistent_pty`` can ``attach``."""
@cached_property
+ @override
def persistent_pty(self) -> PersistentPty: # pyright: ignore[reportIncompatibleVariableOverride]
return PersistentPty(self._client)
@@ -63,5 +66,6 @@ class AsyncExperimental(AsyncExperimentalResource):
"""``client.experimental`` of the async client."""
@cached_property
+ @override
def persistent_pty(self) -> AsyncPersistentPty: # pyright: ignore[reportIncompatibleVariableOverride]
return AsyncPersistentPty(self._client)
diff --git a/src/opencode/lib/_session.py b/src/opencode/lib/_session.py
index c23037b..cf2a09a 100644
--- a/src/opencode/lib/_session.py
+++ b/src/opencode/lib/_session.py
@@ -1,7 +1,7 @@
"""``client.session.run``: create or reuse a session, prompt it, wait until idle, and collect the result.
-The generated ``SessionResource`` / ``AsyncSessionResource`` inherit from the classes here (see
-``codegen/render.py``, ``HELPER_BASES``), so these methods sit next to the generated ones.
+``lib/_resources.py`` mixes these classes into the generated ``SessionResource`` /
+``AsyncSessionResource``, so these methods sit next to the generated ones.
"""
from __future__ import annotations
@@ -12,7 +12,7 @@
import anyio
-from .._types import NOT_GIVEN, Timeout, NotGiven
+from .._types import Omit, omit
from .._resource import SyncAPIResource, AsyncAPIResource
if TYPE_CHECKING:
@@ -20,16 +20,15 @@
from ..types import (
AgentID,
- ModelRef,
SessionID,
SessionInfo,
TokenUsageInfo,
SessionMessageInfo,
SessionStructuredError,
SessionMessageAssistant,
- PromptInputFileAttachment,
)
from ..resources.session import SessionResource, AsyncSessionResource
+ from ..types.shared_params import ModelRef, PromptInputFileAttachment
__all__ = ["AsyncSessionHelpers", "RunResult", "SessionHelpers"]
@@ -130,9 +129,9 @@ def run(
*,
agent: AgentID | None = None,
model: ModelRef | None = None,
- files: Sequence[PromptInputFileAttachment] | NotGiven = NOT_GIVEN,
+ files: Sequence[PromptInputFileAttachment] | Omit = omit,
session_id: SessionID | None = None,
- timeout: Timeout = None,
+ timeout: float | None = None,
) -> RunResult:
"""Runs one prompt to completion and returns what the agent produced.
@@ -159,7 +158,7 @@ def run(
"""
resource = cast("SessionResource", self)
if session_id is None:
- session = resource.create(agent=agent or NOT_GIVEN, model=model or NOT_GIVEN)
+ session = resource.create(agent=agent or omit, model=model or omit)
session_id = session.id
marker = None
else:
@@ -195,9 +194,9 @@ async def run(
*,
agent: AgentID | None = None,
model: ModelRef | None = None,
- files: Sequence[PromptInputFileAttachment] | NotGiven = NOT_GIVEN,
+ files: Sequence[PromptInputFileAttachment] | Omit = omit,
session_id: SessionID | None = None,
- timeout: Timeout = None,
+ timeout: float | None = None,
) -> RunResult:
"""Runs one prompt to completion. See :meth:`SessionHelpers.run`.
@@ -205,7 +204,7 @@ async def run(
"""
resource = cast("AsyncSessionResource", self)
if session_id is None:
- session = await resource.create(agent=agent or NOT_GIVEN, model=model or NOT_GIVEN)
+ session = await resource.create(agent=agent or omit, model=model or omit)
session_id = session.id
marker = None
else:
From 404bd6d19baa999d274d054f4a54ea47a6eca7e9 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 18:12:56 +0530
Subject: [PATCH 28/41] Run CI and the spec sync on the Ferrule toolchain
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
.github/workflows/ci.yml | 35 ++-
.github/workflows/sync-spec.yml | 15 +-
.gitignore | 1 +
scripts/mock | 52 +++++
scripts/opencode/config_models.py | 250 +++++++++++++++++++++
scripts/opencode/contract_to_openapi.py | 277 ++++++++++++++++++++++++
6 files changed, 616 insertions(+), 14 deletions(-)
create mode 100755 scripts/mock
create mode 100644 scripts/opencode/config_models.py
create mode 100644 scripts/opencode/contract_to_openapi.py
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8d64a6f..7ffb923 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,7 +14,7 @@ concurrency:
jobs:
lint:
- name: Lint, types, generated code
+ name: Lint and types
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -26,8 +26,11 @@ jobs:
- run: uv run ruff format --check .
- run: uv run pyright
- run: uv run mypy
- - name: Generated code matches spec/contract.json
- run: uv run python -m codegen --check
+ - name: openapi.json and stainless.yml match spec/contract.json
+ run: |
+ uv run --no-project --python 3.12 python scripts/opencode/contract_to_openapi.py spec/contract.json openapi.json
+ uv run --no-project --python 3.12 python scripts/opencode/config_models.py openapi.json stainless.yml
+ git diff --exit-code -- openapi.json stainless.yml
test:
name: Test (Python ${{ matrix.python }}, ${{ matrix.os }})
@@ -48,8 +51,19 @@ jobs:
with:
enable-cache: true
python-version: ${{ matrix.python }}
+ - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
+ with:
+ node-version: 22
- run: uv sync --locked
- - run: uv run pytest -m "not live" --cov --cov-report=term-missing
+ - name: Start the mock server
+ shell: bash
+ run: ./scripts/mock --daemon
+ # The generated tests run against the mock (pytest-asyncio); the hand-written ones in
+ # tests/custom use anyio and trio, so they run apart.
+ - run: uv run pytest --cov --cov-report=term-missing
+ - run: >-
+ uv run pytest --confcutdir=tests/custom -o asyncio_mode=strict -o addopts="-ra --strict-markers"
+ -m "not live" --cov --cov-append --cov-report=term-missing tests/custom
lowest:
name: Test with lowest supported dependencies
@@ -61,8 +75,13 @@ jobs:
python-version: "3.10"
- run: >-
uv run --isolated --resolution lowest-direct --with-editable '.[pty,aiohttp]'
- --with pytest --with respx --with trio
- pytest -m "not live" -p no:cacheprovider
+ --with pytest --with pytest-asyncio --with respx --with trio --with dirty-equals --with time-machine
+ pytest -p no:cacheprovider --ignore=tests/api_resources
+ - run: >-
+ uv run --isolated --resolution lowest-direct --with-editable '.[pty,aiohttp]'
+ --with pytest --with pytest-asyncio --with respx --with trio
+ pytest -p no:cacheprovider --confcutdir=tests/custom -o asyncio_mode=strict
+ -o addopts="-ra --strict-markers" -m "not live" tests/custom
live:
name: Live server
@@ -80,7 +99,9 @@ jobs:
- run: echo "$GITHUB_WORKSPACE/.github/live/node_modules/.bin" >> "$GITHUB_PATH"
- run: opencode --version
- run: uv sync --locked
- - run: uv run pytest -m live -v
+ - run: >-
+ uv run pytest --confcutdir=tests/custom -o asyncio_mode=strict -o addopts="-ra --strict-markers"
+ -m live -v tests/custom
build:
name: Build distributions
diff --git a/.github/workflows/sync-spec.yml b/.github/workflows/sync-spec.yml
index fdad129..f042765 100644
--- a/.github/workflows/sync-spec.yml
+++ b/.github/workflows/sync-spec.yml
@@ -39,13 +39,12 @@ jobs:
run: |
bun add --exact --ignore-scripts "@opencode/protocol@${{ steps.version.outputs.version }}" "effect@${{ steps.version.outputs.effect }}"
bun run extract.ts
- - name: Regenerate and verify
+ - name: Convert the contract and update the config
+ # Stdlib-only scripts from Ferrule (scripts/opencode/); the SDK itself is regenerated by
+ # sdk.yml once this PR is merged.
run: |
- uv sync --locked
- uv run python -m codegen
- uv run ruff check .
- uv run pyright
- uv run pytest -m "not live"
+ uv run --no-project --python 3.12 python scripts/opencode/contract_to_openapi.py spec/contract.json openapi.json
+ uv run --no-project --python 3.12 python scripts/opencode/config_models.py openapi.json stainless.yml
- run: git diff > sync.patch
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
@@ -76,5 +75,7 @@ jobs:
body: |
Regenerated from `@opencode/protocol@${{ needs.generate.outputs.version }}`.
- Review `api.md` for added, removed or changed operations before merging.
+ Updates `spec/contract.json`, `openapi.json` and the models in `stainless.yml`. Once merged,
+ the `sdk` workflow regenerates the SDK with Ferrule and opens a `ferrule/regenerate` PR
+ with the public API changes.
delete-branch: true
diff --git a/.gitignore b/.gitignore
index 1db7705..645f980 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,4 @@ codegen/extract/.cache/
# MkDocs output
site/
.cache/
+.stdy.log
diff --git a/scripts/mock b/scripts/mock
new file mode 100755
index 0000000..3284b1f
--- /dev/null
+++ b/scripts/mock
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname "$0")/.."
+
+if [[ -n "$1" && "$1" != '--'* ]]; then
+ URL="$1"
+ shift
+else
+ URL="openapi.json"
+fi
+
+# Check if the URL is empty
+if [ -z "$URL" ]; then
+ echo "Error: No OpenAPI spec path/url provided"
+ exit 1
+fi
+
+echo "==> Starting mock server with URL ${URL}"
+
+# Run steady mock on the given spec
+if [ "$1" == "--daemon" ]; then
+ # Pre-install the package so the download doesn't eat into the startup timeout
+ npm exec --package=@stdy/cli@0.22.1 -- steady --version
+
+ npm exec --package=@stdy/cli@0.22.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=repeat --validator-form-array-format=repeat --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" &> .stdy.log &
+
+ # Wait for server to come online via health endpoint (max 30s)
+ echo -n "Waiting for server"
+ attempts=0
+ while ! curl --silent --fail "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1; do
+ if ! kill -0 $! 2>/dev/null; then
+ echo
+ cat .stdy.log
+ exit 1
+ fi
+ attempts=$((attempts + 1))
+ if [ "$attempts" -ge 300 ]; then
+ echo
+ echo "Timed out waiting for Steady server to start"
+ cat .stdy.log
+ exit 1
+ fi
+ echo -n "."
+ sleep 0.1
+ done
+
+ echo
+else
+ npm exec --package=@stdy/cli@0.22.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=repeat --validator-form-array-format=repeat --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL"
+fi
diff --git a/scripts/opencode/config_models.py b/scripts/opencode/config_models.py
new file mode 100644
index 0000000..ea4d399
--- /dev/null
+++ b/scripts/opencode/config_models.py
@@ -0,0 +1,250 @@
+# Copyright 2026 Ferrule contributors
+# SPDX-License-Identifier: Apache-2.0
+# Copied from https://github.com/shrijayan/ferrule/blob/eed5b835840d75ca4e953662731e1acdc6bf90b0/scripts/opencode/config_models.py
+# Modified by The Ferrule Authors: Json is a TypeAlias, for Python 3.10.
+"""Name every schema of the opencode spec as a model in its stainless.yml.
+
+The opencode SDK exports each ``$defs`` entry of ``spec/contract.json`` from ``opencode.types``
+under the name its own generator gave it (``Agent.Info`` -> ``AgentInfo``,
+``Session.NotFoundErrorEncoded`` -> ``SessionNotFoundErrorBody``, ``session.created`` ->
+``SessionCreatedEvent``). ``models:`` is how a stainless.yml keeps such names, so this script
+writes one model per component schema, under ``$shared``, keyed by that name in snake case, and
+the ``custom_casings`` that spell the keys back as the SDK does (``ConfigLSPServer``,
+``MoneyUSD``). Both go between marker comments, so a new spec is applied by running it again;
+the rest of the config is left as written, except that a method of an endpoint whose ``data``
+the SDK returns (``x-opencode-unwrap-data``) gets ``unwrap_response: data``.
+
+``apply`` is pure and deterministic; only ``main`` touches files. See ADR 0015.
+"""
+
+from __future__ import annotations
+
+import re
+import json
+import argparse
+from typing import Any, cast
+from pathlib import Path
+from typing_extensions import TypeAlias
+
+Json: TypeAlias = dict[str, Any]
+
+MODELS_BEGIN = " # written by scripts/opencode/config_models.py"
+MODELS_END = " # "
+CASINGS_BEGIN = "# written by scripts/opencode/config_models.py"
+CASINGS_END = "# "
+SCHEMAS_PREFIX = "#/components/schemas/"
+
+# Words Stainless spells upper-case in class names unless custom_casings says otherwise
+# (crates/converter/src/extensions/casings.rs DEFAULT_INITIALISMS).
+DEFAULT_INITIALISMS = frozenset(
+ {
+ "3ds", "ach", "acl", "ai", "api", "ats", "cpu", "crm", "db", "dns", "e2e", "eeoc", "gb",
+ "gpu", "hd", "hris", "html", "http", "https", "id", "ip", "iso", "jsonl", "kb", "kyb",
+ "kyc", "lfs", "mb", "mp3", "mp4", "nft", "saml", "sdk", "sku", "sms", "ss", "ssh", "sso",
+ "url",
+ }
+) # fmt: skip
+
+_WORD_SPLIT = re.compile(r"[^0-9A-Za-z]+")
+_ACRONYM_BOUNDARY = re.compile(r"([A-Z]+)([A-Z][a-z])")
+_CASE_BOUNDARY = re.compile(r"([a-z0-9])([A-Z])")
+_NUMBERED = re.compile(r"^(.*)_(\d+)$")
+
+
+class ModelsError(ValueError):
+ """Raised when two schemas get one name, or the config has no place for the models."""
+
+
+def pascal(name: str) -> str:
+ """``session.message.user`` -> ``SessionMessageUser``, inner capitals kept."""
+ return "".join(part[0].upper() + part[1:] for part in _WORD_SPLIT.split(name) if part)
+
+
+def words(name: str) -> list[str]:
+ """``ConfigLSPServer`` -> ``["Config", "LSP", "Server"]``, as the SDK splits names."""
+ spaced = _CASE_BOUNDARY.sub(r"\1 \2", _ACRONYM_BOUNDARY.sub(r"\1 \2", name))
+ return [w for w in _WORD_SPLIT.split(spaced.replace(" ", "_")) if w]
+
+
+def python_name(key: str) -> str:
+ """Return the class or alias name the SDK's generator gives the definition ``key``."""
+ suffix = ""
+ numbered = _NUMBERED.match(key)
+ if numbered:
+ key, suffix = numbered.group(1), numbered.group(2)
+ if key == "V2Event":
+ name = "Event"
+ elif key.startswith("V2Event."):
+ name = pascal(key.removeprefix("V2Event.")) + "Event"
+ elif key[:1].islower():
+ name = pascal(key) + "Event"
+ else:
+ name = key.removesuffix("Encoded")
+ if key.endswith("Encoded") and name.endswith("Error"):
+ name += "Body"
+ name = pascal(name)
+ return name + suffix
+
+
+def _ref_counts(node: object, counts: dict[str, int]) -> dict[str, int]:
+ """Count the ``$ref`` to each component schema under ``node``."""
+ if isinstance(node, dict):
+ ref = cast("Json", node).get("$ref")
+ if isinstance(ref, str) and ref.startswith(SCHEMAS_PREFIX):
+ name = ref.removeprefix(SCHEMAS_PREFIX)
+ counts[name] = counts.get(name, 0) + 1
+ for value in cast("Json", node).values():
+ _ref_counts(value, counts)
+ elif isinstance(node, list):
+ for value in cast("list[object]", node):
+ _ref_counts(value, counts)
+ return counts
+
+
+def envelopes(spec: Json) -> set[str]:
+ """Return the schemas used only as the response of an endpoint whose ``data`` is returned.
+
+ Those are not part of the API once unwrapped, and their names are the ones the unwrapped
+ types get (``GenerateTextResponse``), so they are no models.
+ """
+ as_envelope: dict[str, int] = {}
+ for item in cast("Json", spec.get("paths", {})).values():
+ for op in cast("Json", item).values():
+ if isinstance(op, dict) and cast("Json", op).get("x-opencode-unwrap-data"):
+ content = cast("Json", op).get("responses", {}).get("200", {}).get("content", {})
+ schema = content.get("application/json", {}).get("schema", {})
+ ref = schema.get("$ref", "")
+ if ref.startswith(SCHEMAS_PREFIX):
+ name = ref.removeprefix(SCHEMAS_PREFIX)
+ as_envelope[name] = as_envelope.get(name, 0) + 1
+ everywhere = _ref_counts(spec, {})
+ return {name for name, n in as_envelope.items() if everywhere.get(name) == n}
+
+
+def model_entries(spec: Json) -> list[tuple[str, str]]:
+ """``(model key, schema name)`` for every component schema but the envelopes, by key."""
+ schemas = cast("Json", spec.get("components", {})).get("schemas", {})
+ skip = envelopes(spec)
+ entries: dict[str, str] = {}
+ for schema in sorted(cast("Json", schemas)):
+ if schema in skip:
+ continue
+ key = "_".join(w.lower() for w in words(python_name(schema)))
+ if key in entries:
+ msg = f"{schema} and {entries[key]} are both named {key}"
+ raise ModelsError(msg)
+ entries[key] = schema
+ return sorted(entries.items())
+
+
+def default_spelling(word: str) -> str:
+ """How Stainless spells ``word`` (lower case) in a class name without custom casings."""
+ return word.upper() if word in DEFAULT_INITIALISMS else word[:1].upper() + word[1:]
+
+
+def casings(spec: Json) -> dict[str, Json]:
+ """Return the ``custom_casings`` spelling every model key as the SDK names its schema."""
+ wanted: dict[str, str] = {}
+ for _, schema in model_entries(spec):
+ for word in words(python_name(schema)):
+ low = word.lower()
+ if wanted.setdefault(low, word) != word:
+ msg = f"{low} is spelled both {wanted[low]} and {word}"
+ raise ModelsError(msg)
+ out: dict[str, Json] = {}
+ for low, word in sorted(wanted.items()):
+ if word == default_spelling(low):
+ continue
+ if word == low.upper():
+ out[low] = {"initialism": True}
+ elif word == low[:1].upper() + low[1:]:
+ out[low] = {"initialism": False}
+ else:
+ out[low] = {"pascal": word}
+ return out
+
+
+def models_block(spec: Json) -> str:
+ """Return the ``$shared`` resource with every model, between its markers."""
+ lines = [MODELS_BEGIN, " $shared:", " models:"]
+ lines += [f' {key}: "{SCHEMAS_PREFIX}{schema}"' for key, schema in model_entries(spec)]
+ return "\n".join([*lines, MODELS_END])
+
+
+def casings_block(spec: Json) -> str:
+ """Return the top-level ``custom_casings``, between its markers."""
+ lines = [CASINGS_BEGIN, "custom_casings:"]
+ for word, casing in casings(spec).items():
+ (field, value), *_ = casing.items()
+ lines.append(f" {word}: {{{field}: {json.dumps(value)}}}")
+ return "\n".join([*lines, CASINGS_END])
+
+
+def _replace(text: str, markers: tuple[str, str], block: str, anchor: str, *, after: bool) -> str:
+ begin, end = markers
+ if begin in text:
+ head, rest = text.split(begin, 1)
+ if end not in rest:
+ msg = f"{begin.strip()} has no {end.strip()}"
+ raise ModelsError(msg)
+ return head + block + rest.split(end, 1)[1]
+ if anchor not in text:
+ msg = f"the config has no {anchor.strip()!r} line"
+ raise ModelsError(msg)
+ placed = anchor + block + "\n" if after else "\n" + block + anchor
+ return text.replace(anchor, placed, 1)
+
+
+_METHOD_LINE = re.compile(
+ r"^(?P +)(?P\w+): (?Pget|post|put|patch|delete) (?P\S+)$",
+ re.MULTILINE,
+)
+
+
+def unwrapped(spec: Json) -> set[str]:
+ """Return the ``verb path`` endpoints whose ``data`` the SDK returns."""
+ found: set[str] = set()
+ for path, item in cast("Json", spec.get("paths", {})).items():
+ for verb, op in cast("Json", item).items():
+ if isinstance(op, dict) and cast("Json", op).get("x-opencode-unwrap-data"):
+ found.add(f"{verb} {path}")
+ return found
+
+
+def unwrap_methods(config: str, spec: Json) -> str:
+ """Write each method of an unwrapped endpoint as ``{endpoint, unwrap_response: data}``."""
+ wanted = unwrapped(spec)
+
+ def rewrite(m: re.Match[str]) -> str:
+ endpoint = f"{m['verb']} {m['path']}"
+ if endpoint not in wanted or m["name"] == "endpoint":
+ return m.group(0)
+ pad = m["indent"] + " "
+ return f"{m['indent']}{m['name']}:\n{pad}endpoint: {endpoint}\n{pad}unwrap_response: data"
+
+ return _METHOD_LINE.sub(rewrite, config)
+
+
+def apply(config: str, spec: Json) -> str:
+ """``config`` with its models, casings and unwrapped methods rewritten for ``spec``."""
+ models = models_block(spec)
+ text = _replace(config, (MODELS_BEGIN, MODELS_END), models, "\nresources:\n", after=True)
+ casing = casings_block(spec)
+ text = _replace(text, (CASINGS_BEGIN, CASINGS_END), casing, "\nreadme:\n", after=False)
+ return unwrap_methods(text, spec)
+
+
+def main(argv: list[str] | None = None) -> int:
+ """Rewrite the models and casings of a stainless.yml from an OpenAPI document."""
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0] if __doc__ else None)
+ parser.add_argument("spec", type=Path)
+ parser.add_argument("config", type=Path)
+ args = parser.parse_args(argv)
+ config = cast("Path", args.config)
+ spec = json.loads(cast("Path", args.spec).read_text(encoding="utf-8"))
+ config.write_text(apply(config.read_text(encoding="utf-8"), spec), encoding="utf-8")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/opencode/contract_to_openapi.py b/scripts/opencode/contract_to_openapi.py
new file mode 100644
index 0000000..348d37c
--- /dev/null
+++ b/scripts/opencode/contract_to_openapi.py
@@ -0,0 +1,277 @@
+# Copyright 2026 Ferrule contributors
+# SPDX-License-Identifier: Apache-2.0
+# Copied from https://github.com/shrijayan/ferrule/blob/eed5b835840d75ca4e953662731e1acdc6bf90b0/scripts/opencode/contract_to_openapi.py
+# Modified by The Ferrule Authors: Json is a TypeAlias, for Python 3.10.
+"""Convert the opencode SDK's ``spec/contract.json`` into an OpenAPI 3.1 document.
+
+``contract.json`` is exported from the Effect ``HttpApi`` definitions in ``@opencode/protocol``.
+It lists groups of endpoints and a ``$defs`` table of JSON Schemas. This module maps:
+
+- each group to an OpenAPI tag,
+- each endpoint to an operation (``:name`` and ``*`` path segments become ``{name}``),
+- ``params``/``query``/``headers``/``wildcard`` inputs to parameters,
+- ``payload`` inputs to a JSON (``struct``) or octet-stream (``binary``) request body,
+- ``value``/``void``/``stream``/``binary`` successes and tagged errors to responses,
+- ``$defs`` to ``components.schemas``, with Effect's ``Struct`` + ``Record`` intersection and
+ ``NonEmptyArray`` written in the plain form generators read.
+
+``convert`` is pure and deterministic; only ``main`` touches files. See ADR 0015.
+"""
+
+from __future__ import annotations
+
+import sys
+import json
+import argparse
+from typing import Any, cast
+from pathlib import Path
+from typing_extensions import TypeAlias
+
+Json: TypeAlias = dict[str, Any]
+
+DEFS_PREFIX = "#/$defs/"
+COMPONENTS_PREFIX = "#/components/schemas/"
+SOURCE_TO_LOCATION = {"params": "path", "wildcard": "path", "query": "query", "headers": "header"}
+
+
+class ContractError(ValueError):
+ """Raised when ``contract.json`` has a shape this converter does not know."""
+
+
+def rewrite_refs(node: object) -> object:
+ """Return ``node`` with every ``#/$defs/`` reference pointed at ``components.schemas``."""
+ if isinstance(node, dict):
+ out: Json = {}
+ for key, value in cast("Json", node).items():
+ if key == "$schema":
+ continue
+ if key == "$ref" and isinstance(value, str) and value.startswith(DEFS_PREFIX):
+ out[key] = COMPONENTS_PREFIX + value.removeprefix(DEFS_PREFIX)
+ else:
+ out[key] = rewrite_refs(value)
+ return fold_non_empty_array(fold_record(out))
+ if isinstance(node, list):
+ return [rewrite_refs(item) for item in cast("list[object]", node)]
+ return node
+
+
+def fold_non_empty_array(schema: Json) -> Json:
+ """Drop the ``prefixItems`` of Effect's ``NonEmptyArray``: its one entry repeats ``items``.
+
+ ``minItems: 1`` already says the array is not empty; generators that read only ``items``
+ (Forge) otherwise see no item type. A real tuple (``prefixItems`` that differ) is kept.
+ """
+ prefix = schema.get("prefixItems")
+ if (
+ schema.get("type") != "array"
+ or not isinstance(prefix, list)
+ or cast("list[object]", prefix) != [schema.get("items")]
+ ):
+ return schema
+ return {key: value for key, value in schema.items() if key != "prefixItems"}
+
+
+def _is_record(member: object) -> bool:
+ return isinstance(member, dict) and set(cast("Json", member)) == {
+ "type",
+ "additionalProperties",
+ }
+
+
+def fold_record(schema: Json) -> Json:
+ """Fold Effect's ``Struct`` + ``Record`` intersection into one object schema.
+
+ Effect encodes ``Schema.Struct({...}, Schema.Record(...))`` as an object whose ``allOf``
+ holds only ``{"type": "object", "additionalProperties": X}``. That is the same schema as the
+ object with ``additionalProperties: X``, which generators handle without an ``allOf``.
+ """
+ members = schema.get("allOf")
+ if (
+ schema.get("type") != "object"
+ or "additionalProperties" in schema
+ or not isinstance(members, list)
+ or len(cast("list[object]", members)) != 1
+ or not _is_record(cast("list[object]", members)[0])
+ ):
+ return schema
+ record = cast("list[Json]", members)[0]
+ folded = {key: value for key, value in schema.items() if key != "allOf"}
+ folded["additionalProperties"] = record["additionalProperties"]
+ return folded
+
+
+def convert_path(path: str) -> str:
+ """Turn ``/a/:id/b/*`` into the OpenAPI template ``/a/{id}/b/{path}``."""
+ segments: list[str] = []
+ for segment in path.split("/"):
+ if segment.startswith(":"):
+ segments.append("{" + segment[1:] + "}")
+ elif segment == "*":
+ segments.append("{path}")
+ else:
+ segments.append(segment)
+ return "/".join(segments)
+
+
+def _is_object(schema: Json) -> bool:
+ if schema.get("type") == "object":
+ return True
+ variants = cast("list[Json]", schema.get("anyOf", []))
+ return any(variant.get("type") == "object" for variant in variants)
+
+
+def _parameter(item: Json) -> Json:
+ location = SOURCE_TO_LOCATION[item["source"]]
+ schema = cast("Json", rewrite_refs(item["schema"]))
+ param: Json = {
+ "name": item["name"],
+ "in": location,
+ "required": location == "path" or not item["optional"],
+ "schema": schema,
+ }
+ if location == "query" and _is_object(schema):
+ # Forge covers only ``form`` style; nested keys are encoded by query_settings.
+ param["style"] = "form"
+ param["explode"] = True
+ return param
+
+
+def _request_body(endpoint: Json, payload: list[Json]) -> Json | None:
+ if not payload:
+ return None
+ mode = cast("Json", endpoint.get("payload") or {}).get("mode", "struct")
+ if mode == "binary":
+ content: Json = {"application/octet-stream": {"schema": {"type": "string", "format": "binary"}}}
+ return {"required": True, "content": content}
+ if mode != "struct":
+ msg = f"{endpoint['operationId']}: unknown payload mode {mode!r}"
+ raise ContractError(msg)
+ properties = {item["name"]: rewrite_refs(item["schema"]) for item in payload}
+ required = [item["name"] for item in payload if not item["optional"]]
+ schema: Json = {"type": "object", "properties": properties}
+ if required:
+ schema["required"] = required
+ return {"required": bool(required), "content": {"application/json": {"schema": schema}}}
+
+
+def _success(endpoint: Json) -> tuple[str, Json]:
+ success = cast("Json", endpoint["success"])
+ status = str(success["status"])
+ kind = success["kind"]
+ description = endpoint.get("summary") or "Success"
+ response: Json = {"description": description}
+ if kind == "void":
+ return status, response
+ if kind == "value":
+ media = "application/json"
+ schema = rewrite_refs(success["schema"])
+ elif kind == "stream":
+ media = "text/event-stream"
+ schema = rewrite_refs(success["schema"])
+ elif kind == "binary":
+ media = "application/octet-stream"
+ schema = {"type": "string", "format": "binary"}
+ else:
+ msg = f"{endpoint['operationId']}: unknown success kind {kind!r}"
+ raise ContractError(msg)
+ response["content"] = {media: {"schema": schema}}
+ return status, response
+
+
+def _errors(endpoint: Json) -> dict[str, Json]:
+ by_status: dict[str, list[Json]] = {}
+ for error in cast("list[Json]", endpoint["errors"]):
+ by_status.setdefault(str(error["status"]), []).append(error)
+ responses: dict[str, Json] = {}
+ for status in sorted(by_status):
+ errors = by_status[status]
+ schemas = [rewrite_refs(error["schema"]) for error in errors]
+ schema = schemas[0] if len(schemas) == 1 else {"anyOf": schemas}
+ responses[status] = {
+ "description": ", ".join(error["tag"] for error in errors),
+ "content": {"application/json": {"schema": schema}},
+ }
+ return responses
+
+
+def convert_endpoint(group: str, endpoint: Json) -> Json:
+ """Map one contract endpoint to an OpenAPI operation object."""
+ inputs = cast("list[Json]", endpoint["input"])
+ for item in inputs:
+ if item["source"] not in SOURCE_TO_LOCATION and item["source"] != "payload":
+ msg = f"{endpoint['operationId']}: unknown input source {item['source']!r}"
+ raise ContractError(msg)
+ operation: Json = {"operationId": endpoint["operationId"], "tags": [group]}
+ for key in ("summary", "description"):
+ if endpoint.get(key):
+ operation[key] = endpoint[key]
+ if endpoint["deprecated"]:
+ operation["deprecated"] = True
+ parameters = [_parameter(item) for item in inputs if item["source"] != "payload"]
+ if parameters:
+ operation["parameters"] = parameters
+ body = _request_body(endpoint, [item for item in inputs if item["source"] == "payload"])
+ if body is not None:
+ operation["requestBody"] = body
+ status, success = _success(endpoint)
+ operation["responses"] = {status: success, **_errors(endpoint)}
+ operation["x-opencode-client-path"] = list(endpoint["clientPath"])
+ if cast("Json", endpoint["success"]).get("unwrapData"):
+ # The SDK returns the `data` of this envelope (config_models.py: `unwrap_response`).
+ operation["x-opencode-unwrap-data"] = True
+ return operation
+
+
+def convert(contract: Json) -> Json:
+ """Convert a parsed ``contract.json`` into an OpenAPI 3.1 document."""
+ paths: dict[str, Json] = {}
+ tags: list[Json] = []
+ for group in cast("list[Json]", contract["groups"]):
+ name = group["name"]
+ tags.append({"name": name})
+ for endpoint in cast("list[Json]", group["endpoints"]):
+ method = str(endpoint["method"]).lower()
+ item = paths.setdefault(convert_path(endpoint["path"]), {})
+ if method in item:
+ msg = f"duplicate operation {endpoint['method']} {endpoint['path']}"
+ raise ContractError(msg)
+ item[method] = convert_endpoint(name, endpoint)
+ definitions = cast("Json", contract["definitions"])
+ schemas = {name: rewrite_refs(definitions[name]) for name in sorted(definitions)}
+ return {
+ "openapi": "3.1.0",
+ "info": {
+ "title": "OpenCode",
+ "version": contract["opencodeVersion"],
+ "x-contract-generator": contract["generator"],
+ },
+ "tags": tags,
+ "paths": {path: paths[path] for path in sorted(paths)},
+ "components": {"schemas": schemas},
+ }
+
+
+def dumps(document: Json) -> str:
+ """Serialise ``document`` the same way every time."""
+ return json.dumps(document, indent=2, ensure_ascii=False) + "\n"
+
+
+def main(argv: list[str] | None = None) -> int:
+ """Read ``contract.json`` and write the OpenAPI document."""
+ parser = argparse.ArgumentParser(description="Convert contract.json to OpenAPI 3.1.")
+ parser.add_argument("contract", type=Path)
+ parser.add_argument("output", type=Path)
+ args = parser.parse_args(argv)
+ contract_path = cast("Path", args.contract)
+ output_path = cast("Path", args.output)
+ try:
+ document = convert(json.loads(contract_path.read_text(encoding="utf-8")))
+ except (ContractError, KeyError) as exc:
+ sys.stderr.write(f"error: {contract_path}: {exc}\n")
+ return 1
+ output_path.write_text(dumps(document), encoding="utf-8")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
From d89acdd9e23e60235c5ab716b341c90ee944769c Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 18:12:56 +0530
Subject: [PATCH 29/41] Credit the Apache-2.0 generated code and runtime
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
LICENSE-APACHE | 202 +++++++++++++++++++++++++++++++++++++++++++++++++
NOTICE | 27 +++++++
pyproject.toml | 6 +-
3 files changed, 232 insertions(+), 3 deletions(-)
create mode 100644 LICENSE-APACHE
create mode 100644 NOTICE
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE-APACHE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..6a9f996
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,27 @@
+opencode-python-sdk
+Copyright 2026 Shrijayan Rajendran
+
+The hand-written code of this SDK (src/opencode/lib/, the event hub, RPC, PTY, server and
+service modules, testing.py, tests/custom/, examples/ outside examples/agent/, docs/) is under
+the MIT licence in LICENSE.
+
+The rest of src/opencode/, tests/ (outside tests/custom/), examples/agent/, api.md and the
+_agent/ files are generated by Ferrule (https://github.com/shrijayan/ferrule) from openapi.json
+and stainless.yml, and are under the Apache License, Version 2.0, in LICENSE-APACHE. ferrule.lock
+lists them.
+
+Ferrule's Python runtime (src/opencode/_base_client.py, _compat.py, _constants.py,
+_exceptions.py, _files.py, _models.py, _qs.py, _resource.py, _response.py, _streaming.py,
+_types.py, py.typed and _utils/) is derived from the Stainless-generated OneBusAway Python SDK:
+ Repository: https://github.com/OneBusAway/python-sdk
+ Commit: 4c20fa834c2a1237ef046e4894af60cd02fb3bcf (release 1.30.0)
+ Licence: Apache License, Version 2.0
+ Copyright: Copyright 2026 Onebusaway SDK
+ Changes: Modified by The Ferrule Authors: names and settings replaced by this SDK's.
+
+As noted in their headers, _streaming.py contains code initially copied from httpx-sse
+(https://github.com/florimondmanca/httpx-sse, MIT) and _utils/_datetime_parse.py contains code
+from pydantic (https://github.com/pydantic/pydantic, MIT).
+
+scripts/opencode/contract_to_openapi.py and scripts/opencode/config_models.py are copied from
+Ferrule (Apache License, Version 2.0, Copyright 2026 Ferrule contributors).
diff --git a/pyproject.toml b/pyproject.toml
index 87021ab..51204a0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,8 +3,8 @@ name = "opencode-python-sdk"
dynamic = ["version"]
description = "Typed Python client for the OpenCode 2.x server API"
readme = "README.md"
-license = "MIT"
-license-files = ["LICENSE"]
+license = "MIT AND Apache-2.0"
+license-files = ["LICENSE", "LICENSE-APACHE", "NOTICE"]
requires-python = ">=3.10"
authors = [{ name = "Shrijayan Rajendran" }]
keywords = ["opencode", "ai", "agents", "coding-agent", "sdk", "client"]
@@ -87,7 +87,7 @@ raw-options = { local_scheme = "no-local-version", fallback_version = "0.0.0" }
packages = ["src/opencode"]
[tool.hatch.build.targets.sdist]
-include = ["src/opencode", "tests", "README.md", "LICENSE", "CHANGELOG.md", "api.md"]
+include = ["src/opencode", "tests", "README.md", "LICENSE", "LICENSE-APACHE", "NOTICE", "CHANGELOG.md", "api.md"]
[tool.uv]
required-version = ">=0.8"
From b28d267dd3911feb4609b7f484904401ff828b73 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 18:14:57 +0530
Subject: [PATCH 30/41] Document how the SDK is generated with Ferrule
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
CONTRIBUTING.md | 61 ++++++++++++++++++-----------------
README.md | 17 ++++++----
docs/hooks/types_reference.py | 21 ++++++------
docs/project/how-its-built.md | 48 ++++++++++++++++-----------
4 files changed, 80 insertions(+), 67 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3d8a84a..56e2361 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,20 +1,25 @@
# Contributing
-Thanks for helping. This project is a generated client with a small hand-written runtime, so most changes fall
-into one of two places.
+Thanks for helping. Most of this client is generated by [Ferrule](https://github.com/shrijayan/ferrule) from
+`openapi.json` and `stainless.yml`; the SDK's own features are written by hand next to it.
## Setup
```bash
uv sync
-uv run pytest -m "not live"
+./scripts/mock --daemon # the mock server the generated tests call (needs Node)
+uv run pytest
+uv run pytest --confcutdir=tests/custom -o asyncio_mode=strict -o addopts="-ra --strict-markers" -m "not live" tests/custom
```
+The generated tests use pytest-asyncio; the hand-written ones in `tests/custom` use anyio and trio, so they run
+apart.
+
Live tests start a real `opencode serve` and need the [OpenCode CLI](https://opencode.ai) on `PATH`. They make no
model calls and use a temporary data directory:
```bash
-uv run pytest -m live
+uv run pytest --confcutdir=tests/custom -o asyncio_mode=strict -o addopts="-ra --strict-markers" -m live tests/custom
```
## Checks
@@ -26,8 +31,7 @@ uv run ruff check .
uv run ruff format --check .
uv run pyright
uv run mypy
-uv run python -m codegen --check
-uv run pytest
+uv run pytest # and tests/custom, as above
```
## Documentation
@@ -43,21 +47,11 @@ uv run mkdocs build --strict
### Docs must keep up with the code
-A change to the public API is not done until the docs cover it. CI enforces this:
-
-| Check | Where | Fails when |
-| --- | --- | --- |
-| `tests/test_docs_coverage.py` | `pytest` job | A resource, method, `opencode.__all__` name or `opencode.types` model is not in the reference; an exported class or one of its public methods has no docstring; a resource group is missing from `docs/guides/index.md`; a guide mentions a method or type that does not exist |
-| ruff `D1` rules | Lint job | A public function in a public module has no docstring |
-| `mkdocs build --strict` | Docs workflow | A link or cross-reference is broken |
-
-Each failure names what is missing and the file to change. In practice:
-
-- **New endpoint or resource group** (from a spec sync): resource and type pages are generated from the code, so
- they appear on their own. Add a row for a new group to `docs/guides/index.md`.
-- **New hand-written class or function exported from `opencode`**: give it a docstring and add a `::: opencode.Name`
- directive to the right page in `docs/reference/`.
-- **Renamed or removed API**: update every guide that mentions it.
+A change to the public API is not done until the docs cover it. `mkdocs build --strict` (docs workflow) fails on a
+broken link or cross-reference. Resource and type pages are generated from the code, so a new endpoint appears on
+its own; add a row for a new resource group to `docs/guides/index.md`, give a new hand-written export a docstring
+and a `::: opencode.Name` directive in `docs/reference/`, and update every guide that mentions renamed or removed
+API.
## Layout
@@ -65,29 +59,36 @@ Each failure names what is missing and the file to change. In practice:
| --- | --- | --- |
| `codegen/extract/` | Exports the OpenCode contract from `@opencode/protocol` | Yes |
| `spec/contract.json` | The exported contract | No, run the extractor |
-| `codegen/*.py` | The Python code generator | Yes |
-| `src/opencode/types/`, `src/opencode/resources/`, `src/opencode/_errors.py`, `api.md` | Generated code | No |
-| `src/opencode/_*.py` (other) | Transport, retries, streaming, pagination, launcher | Yes |
-| `tests/` | Tests | Yes |
+| `scripts/opencode/` | Contract to `openapi.json`, and the model names in `stainless.yml` (from Ferrule) | Rarely |
+| `openapi.json`, `stainless.yml` | What Ferrule generates from; `stainless.yml` also lists the files Ferrule keeps | `stainless.yml` yes |
+| Files listed in `ferrule.lock` | Generated code, tests and runtime | Prefer not; edits are kept by a three-way merge |
+| `src/opencode/lib/`, `_events.py`, `_rpc.py`, `_pty.py`, `_server.py`, `_service.py`, `service.py`, `testing.py`, `_errors.py` | This SDK's own features | Yes |
+| `tests/custom/` | Tests of the hand-written code | Yes |
## Updating to a new OpenCode version
-The `Sync OpenCode spec` workflow does this nightly and opens a pull request. To do it locally:
+The `Sync OpenCode spec` workflow does this nightly and opens a pull request that updates `spec/contract.json`,
+`openapi.json` and `stainless.yml`. When it is merged, the `sdk` workflow regenerates the SDK with Ferrule and opens
+a `ferrule/regenerate` pull request labelled with the semver bump of the public API change. To do it locally:
```bash
cd codegen/extract
bun add --exact --ignore-scripts @opencode/protocol@ effect@
bun run extract.ts
cd ../..
-uv run python -m codegen
+uv run --no-project --python 3.12 python scripts/opencode/contract_to_openapi.py spec/contract.json openapi.json
+uv run --no-project --python 3.12 python scripts/opencode/config_models.py openapi.json stainless.yml
+ferrule regenerate --repo . --spec openapi.json --config stainless.yml
```
-Then review the diff of `api.md` and update the README version table if the OpenCode minor version changed.
+`ferrule regenerate` commits the new generation on the `generated` branch and merges it into the current branch,
+so hand-written code, edits to generated files and the `keep_files` of `stainless.yml` survive. Review the diff of
+`api.md`, and update the README version table if the OpenCode minor version changed.
## Changing generated code
-Change the generator in `codegen/`, run `uv run python -m codegen`, and commit both the generator change and the
-regenerated files. Never edit generated files directly; CI will reject the change.
+Change `stainless.yml` (or the spec conversion) and regenerate, or fix the generator in Ferrule. A small edit to a
+generated file is kept by the next regeneration's three-way merge, but a change in `src/opencode/lib/` is clearer.
## Releases
diff --git a/README.md b/README.md
index 30d2e8f..78a23c2 100644
--- a/README.md
+++ b/README.md
@@ -521,18 +521,21 @@ changes. See the [releases page](https://github.com/shrijayan/opencode-python-sd
## How it is built
```
-@opencode/protocol (npm) ──bun codegen/extract──▶ spec/contract.json ──python -m codegen──▶ src/opencode
+@opencode/protocol (npm) ─bun codegen/extract─▶ spec/contract.json ─scripts/opencode─▶ openapi.json + stainless.yml ─ferrule─▶ src/opencode
```
1. `codegen/extract/extract.ts` loads `ClientApi` from the published `@opencode/protocol` package and exports
every endpoint, its inputs, outputs and declared errors, and every schema as JSON Schema.
-2. `python -m codegen` turns the contract into pydantic models (`opencode/types`), resources
- (`opencode/resources`), error classes (`opencode/_errors.py`) and [api.md](api.md).
-3. The transport, retries, streaming, pagination and server launcher are written by hand.
+2. `scripts/opencode/contract_to_openapi.py` turns the contract into `openapi.json`, and
+ `scripts/opencode/config_models.py` keeps the model names in `stainless.yml`.
+3. [Ferrule](https://github.com/shrijayan/ferrule) generates the client from those two files: models
+ (`opencode/types`), resources (`opencode/resources`), the runtime (transport, retries, streaming, pagination),
+ the tests under `tests/` and [api.md](api.md). `ferrule.lock` lists every generated file.
+4. This SDK's own features (the event hub, typed RPC, PTY connections, the server launcher, `client.session.run`,
+ typed errors) are written by hand in `src/opencode/lib/` and the modules next to it, and survive regeneration.
-Generated code is committed. CI regenerates it and fails if anything changed. See
-[CONTRIBUTING.md](CONTRIBUTING.md).
+See [CONTRIBUTING.md](CONTRIBUTING.md).
## License
-MIT
+MIT for the hand-written code; the generated code and runtime are Apache-2.0. See [NOTICE](NOTICE).
diff --git a/docs/hooks/types_reference.py b/docs/hooks/types_reference.py
index 674dc09..ccf713a 100644
--- a/docs/hooks/types_reference.py
+++ b/docs/hooks/types_reference.py
@@ -1,6 +1,6 @@
"""MkDocs hook that generates the resource and type reference pages and fills version placeholders.
-`opencode.types` holds ~640 generated models. One page per model would bloat the nav and one page for all of them
+`opencode.types` holds several hundred generated models. One page per model would bloat the nav and one page for all of them
would be unusable, so this groups them by domain (the leading word of the class name, with events and session
messages split out) and writes one page per group. Pages are generated at build time from the source, so they
never drift from the code.
@@ -18,7 +18,7 @@
from mkdocs.structure.files import Files
ROOT = Path(__file__).resolve().parents[2]
-TYPES_SRC = ROOT / "src/opencode/types/_generated.py"
+TYPES_SRC = ROOT / "src/opencode/types/__init__.py"
RESOURCES_DIR = ROOT / "src/opencode/resources"
TYPES_DIR = "reference/types"
MIN_GROUP = 4
@@ -35,16 +35,15 @@
def _public_names() -> list[str]:
+ """The models and aliases ``opencode.types`` re-exports (``from .x import X as X``)."""
tree = ast.parse(TYPES_SRC.read_text())
- names: list[str] = []
- for node in tree.body:
- if isinstance(node, ast.ClassDef):
- names.append(node.name)
- elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
- names.append(node.target.id)
- elif isinstance(node, ast.Assign) and len(node.targets) == 1 and isinstance(node.targets[0], ast.Name):
- names.append(node.targets[0].id)
- return [n for n in names if not n.startswith("_") and n[0].isupper() and n != "__all__"]
+ names = {
+ alias.asname or alias.name
+ for node in tree.body
+ if isinstance(node, ast.ImportFrom)
+ for alias in node.names
+ }
+ return sorted(n for n in names if not n.startswith("_") and n[0].isupper())
def _group_of(name: str) -> str:
diff --git a/docs/project/how-its-built.md b/docs/project/how-its-built.md
index 6ee6a94..1e89077 100644
--- a/docs/project/how-its-built.md
+++ b/docs/project/how-its-built.md
@@ -1,10 +1,10 @@
# How it's built
-Most of this package is generated. The models, resources, error classes and `api.md` come from OpenCode's own
-contract; the transport, retries, streaming, pagination and server launcher are written by hand.
+Most of this package is generated by [Ferrule](https://github.com/shrijayan/ferrule) from OpenCode's own contract;
+this SDK's own features are written by hand next to the generated code.
```text
-@opencode/protocol (npm) ──bun codegen/extract──▶ spec/contract.json ──python -m codegen──▶ src/opencode
+@opencode/protocol (npm) ─bun codegen/extract─▶ spec/contract.json ─scripts/opencode─▶ openapi.json + stainless.yml ─ferrule─▶ src/opencode
```
## 1. Extract the contract
@@ -13,32 +13,42 @@ contract; the transport, retries, streaming, pagination and server launcher are
`HttpApi` definition the official TypeScript client is built from. It exports every endpoint with its inputs,
outputs and declared errors, and every schema as JSON Schema, into `spec/contract.json`.
-Because the contract comes from the server's source of truth rather than a hand-written OpenAPI file, method names
-and request shapes match the TypeScript client exactly.
+## 2. Convert it to OpenAPI
-## 2. Generate Python
+`scripts/opencode/contract_to_openapi.py` turns the contract into `openapi.json`, and
+`scripts/opencode/config_models.py` names every schema as a model in `stainless.yml`, so the types keep the names
+this SDK has always exported (`SessionInfo`, `AgentInfo`). Both are plain Python with no dependencies, and give
+byte-identical output for the same contract.
-`python -m codegen` reads the contract and writes:
+## 3. Generate with Ferrule
+
+Ferrule reads `openapi.json` and `stainless.yml` and writes:
| Output | What it contains |
| --- | --- |
-| `src/opencode/types/` | pydantic models for every schema, discriminated unions with `Unknown...` fallbacks, open enums |
+| `src/opencode/types/` | pydantic models for responses, `TypedDict` params for requests |
| `src/opencode/resources/` | A sync and an async class per API group, with typed arguments and docstrings |
-| `src/opencode/_errors.py` | An exception class for every declared server error |
-| `api.md` | The method table |
+| `src/opencode/_client.py` | `Opencode` and `AsyncOpencode` |
+| `src/opencode/_base_client.py`, `_streaming.py`, `pagination.py`, ... | The runtime: requests, auth, retries, timeouts, server-sent events, cursor pages |
+| `tests/`, `examples/agent/`, `api.md` | Tests against a mock server, runnable recipes, the method table |
+
+`ferrule.lock` lists every generated file with its hash.
-## 3. Hand-written runtime
+## 4. Hand-written features
| Module | Responsibility |
| --- | --- |
-| `_base_client.py` | Request building, auth, retries with backoff, timeouts |
-| `_pagination.py` | Cursor pages and the async paginator |
-| `_streaming.py` | Server-sent event decoding |
-| `_server.py` | `OpencodeServer` |
-| `_models.py`, `_types.py` | Model base class, `NOT_GIVEN`, open enums |
+| `lib/_client.py` | The clients `opencode.Opencode` exports: `directory`, `transport`, `events`, `rpc`, `pty`, `session.run`, `from_service`, `wait_until_ready` |
+| `_events.py` | The event hub behind `client.events` |
+| `_rpc.py`, `_pty.py` | Typed plugin RPC and PTY connections |
+| `_server.py`, `_service.py`, `service.py` | `OpencodeServer` and the background service |
+| `_errors.py` | An exception class for every declared server error |
+| `testing.py` | The fake server |
## Keeping it current
-Generated code is committed. CI regenerates it and fails if anything changed, so the committed code always matches
-`spec/contract.json`. A nightly workflow checks for new `@opencode/protocol` releases and opens a pull request with
-the regenerated client. See [Contributing](contributing.md) for running it locally.
+A nightly workflow checks for new `@opencode/protocol` releases and opens a pull request with the new contract,
+`openapi.json` and `stainless.yml`. Merging it runs Ferrule, which commits the new generation on the `generated`
+branch and opens a `ferrule/regenerate` pull request that merges it three ways, so hand-written code and edits to
+generated files survive, labelled with the semver bump of the public API change. See
+[Contributing](contributing.md) for running it locally.
From bcd3c47cb29e7603b0e714f1e525e39bb834d02c Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 18:17:48 +0530
Subject: [PATCH 31/41] Note the Ferrule regeneration in the changelog
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
CHANGELOG.md | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0396e36..7f76643 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,28 @@ Releases are published automatically on every merge to `main`. Release notes for
## Unreleased
+### Breaking: generated with Ferrule
+
+The client is now generated by [Ferrule](https://github.com/shrijayan/ferrule) from `openapi.json` and
+`stainless.yml` (both made from `spec/contract.json`), in the layout Stainless-generated SDKs use. The hand-written
+features (events, RPC, PTY, the server launcher, `session.run`, typed errors) are unchanged in use.
+
+- Inline types are nested in the module of the model that holds them instead of exported from `opencode.types`
+ (`opencode.types.ConfigInfoCompaction` is `opencode.types.shared.config_info.Compaction`); named schemas keep
+ their names.
+- Requests take `TypedDict` params (`opencode.types.shared_params.ModelRef`, by Python field name:
+ `{"id": ..., "provider_id": ...}`) and return pydantic models.
+- Optional arguments default to `omit` (`opencode.omit`) instead of `NOT_GIVEN`.
+- `file.write(path, body)` takes the bytes (or a path) as `body` instead of `payload`; `shell.create(timeout=)` is
+ `api_timeout=`; `rpc.call(method, rpc_id=...)` takes `rpc_id` by keyword.
+- Methods that returned an envelope's `data` still do; the envelope and wrapper types (`GenerateTextResponseData`,
+ `Vcs*Response`, ...) are gone, and union types have no `Unknown*` fallback classes.
+- Error bodies of non-2xx responses are not typed models (`*ErrorBody`); the typed exceptions remain, with the body
+ fields as attributes.
+- `client.page()` and `client.stream()` are gone; `client.get_api_list()` and `stream=True` cover them.
+- Licence: the generated code and the runtime are Apache-2.0 (`LICENSE-APACHE`, `NOTICE`); the hand-written code
+ stays MIT.
+
- `client.events`: a shared event stream with one connection for all subscribers, bounded per-subscriber queues,
`server.connected` replay, `of_type` / `for_session` / `where` filters, `on_activity` keepalive hook, cancellation,
and opt-in reconnection with backoff (`EventHub(client, reconnect=True)`).
From 37ca655b27bb85165fc5daed8082002e634fff4e Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 18:18:00 +0530
Subject: [PATCH 32/41] Fix the file.write example in the changelog
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f76643..56fe0bb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,7 +17,7 @@ features (events, RPC, PTY, the server launcher, `session.run`, typed errors) ar
- Requests take `TypedDict` params (`opencode.types.shared_params.ModelRef`, by Python field name:
`{"id": ..., "provider_id": ...}`) and return pydantic models.
- Optional arguments default to `omit` (`opencode.omit`) instead of `NOT_GIVEN`.
-- `file.write(path, body)` takes the bytes (or a path) as `body` instead of `payload`; `shell.create(timeout=)` is
+- `file.write(path=..., body=...)` takes the bytes (or a local file path) as `body` instead of `payload`; `shell.create(timeout=)` is
`api_timeout=`; `rpc.call(method, rpc_id=...)` takes `rpc_id` by keyword.
- Methods that returned an envelope's `data` still do; the envelope and wrapper types (`GenerateTextResponseData`,
`Vcs*Response`, ...) are gone, and union types have no `Unknown*` fallback classes.
From 73cedf36dd914adefffca2d0786dd9d0685b094d Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 18:19:20 +0530
Subject: [PATCH 33/41] Pin the Ferrule action
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
.github/workflows/sdk.yml | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml
index fd4c75f..1b56584 100644
--- a/.github/workflows/sdk.yml
+++ b/.github/workflows/sdk.yml
@@ -22,10 +22,13 @@ jobs:
with:
fetch-depth: 0 # the previous release and the generated branch
persist-credentials: false
- - uses: shrijayan/ferrule/action@0000000000000000000000000000000000000000
+ # Pinned to a Ferrule commit; bump it to take generator fixes. Ferrule is not on PyPI yet, so
+ # the action installs it from its own checkout.
+ - uses: shrijayan/ferrule/action@05d458d6594545d0f0647273b2df68fcc41143f9
with:
spec: openapi.json
config: stainless.yml
verify: true # fails on an unlabeled breaking change
open-pr: true # labels major / minor / patch
+ ferrule-version: source
github-token: ${{ secrets.GITHUB_TOKEN }}
From 7c8411735af0afcb91898e9fd27ad5a455e2b051 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 18:32:15 +0530
Subject: [PATCH 34/41] Take the config scripts' biome-formatted casings
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
scripts/opencode/config_models.py | 4 ++--
scripts/opencode/contract_to_openapi.py | 2 +-
stainless.yml | 8 ++++----
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/scripts/opencode/config_models.py b/scripts/opencode/config_models.py
index ea4d399..04a5d0e 100644
--- a/scripts/opencode/config_models.py
+++ b/scripts/opencode/config_models.py
@@ -1,6 +1,6 @@
# Copyright 2026 Ferrule contributors
# SPDX-License-Identifier: Apache-2.0
-# Copied from https://github.com/shrijayan/ferrule/blob/eed5b835840d75ca4e953662731e1acdc6bf90b0/scripts/opencode/config_models.py
+# Copied from https://github.com/shrijayan/ferrule/blob/ea62784146f05b4f15ccdf269f30f6fcf7b98c60/scripts/opencode/config_models.py
# Modified by The Ferrule Authors: Json is a TypeAlias, for Python 3.10.
"""Name every schema of the opencode spec as a model in its stainless.yml.
@@ -176,7 +176,7 @@ def casings_block(spec: Json) -> str:
lines = [CASINGS_BEGIN, "custom_casings:"]
for word, casing in casings(spec).items():
(field, value), *_ = casing.items()
- lines.append(f" {word}: {{{field}: {json.dumps(value)}}}")
+ lines.append(f" {word}: {{ {field}: {json.dumps(value)} }}")
return "\n".join([*lines, CASINGS_END])
diff --git a/scripts/opencode/contract_to_openapi.py b/scripts/opencode/contract_to_openapi.py
index 348d37c..8b112c0 100644
--- a/scripts/opencode/contract_to_openapi.py
+++ b/scripts/opencode/contract_to_openapi.py
@@ -1,6 +1,6 @@
# Copyright 2026 Ferrule contributors
# SPDX-License-Identifier: Apache-2.0
-# Copied from https://github.com/shrijayan/ferrule/blob/eed5b835840d75ca4e953662731e1acdc6bf90b0/scripts/opencode/contract_to_openapi.py
+# Copied from https://github.com/shrijayan/ferrule/blob/ea62784146f05b4f15ccdf269f30f6fcf7b98c60/scripts/opencode/contract_to_openapi.py
# Modified by The Ferrule Authors: Json is a TypeAlias, for Python 3.10.
"""Convert the opencode SDK's ``spec/contract.json`` into an OpenAPI 3.1 document.
diff --git a/stainless.yml b/stainless.yml
index 19b0b82..0b4b66c 100644
--- a/stainless.yml
+++ b/stainless.yml
@@ -111,10 +111,10 @@ targets:
# written by scripts/opencode/config_models.py
custom_casings:
- id1: {initialism: true}
- lsp: {initialism: true}
- rtc: {initialism: true}
- usd: {initialism: true}
+ id1: { initialism: true }
+ lsp: { initialism: true }
+ rtc: { initialism: true }
+ usd: { initialism: true }
#
readme:
example_requests:
From 13458f3ce7eaeb9c3ef2a8ccfa47749c6ee1f17c Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 18:33:54 +0530
Subject: [PATCH 35/41] chore: regenerate SDK
Ferrule-Generated-From: 253c64de074e9781ab5c4e835b1e4e8194a06339
---
ferrule.lock | 2 +-
scripts/mock | 0
2 files changed, 1 insertion(+), 1 deletion(-)
mode change 100755 => 100644 scripts/mock
diff --git a/ferrule.lock b/ferrule.lock
index 0df89f6..2c008ee 100644
--- a/ferrule.lock
+++ b/ferrule.lock
@@ -1,5 +1,5 @@
{
- "config_sha256": "2fcbc78b15df7c2fc79d80d5c93fe2f98388be4906ec370440ea42d9bbee3e56",
+ "config_sha256": "bc33f321671ad32d060f87ea334430e7ef4e78aeba6e01eab9a98f97747d5aeb",
"ferrule_version": "0.1.0",
"files": {
"AGENTS.md": "7f135189e8ab203aea9abecf018f905e395767cd38c24c2d22c3c1230c04c5e5",
diff --git a/scripts/mock b/scripts/mock
old mode 100755
new mode 100644
From 855f6b838dd8492386e324c539944cb9c73967b5 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 18:37:12 +0530
Subject: [PATCH 36/41] Make scripts/mock executable again
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
scripts/mock | 0
1 file changed, 0 insertions(+), 0 deletions(-)
mode change 100644 => 100755 scripts/mock
diff --git a/scripts/mock b/scripts/mock
old mode 100644
new mode 100755
From 16df5c9ec7ded52274fedbd05cd3647a17c2a400 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 18:42:43 +0530
Subject: [PATCH 37/41] Pin the Ferrule action to the commit with the
regenerate fixes
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
.github/workflows/sdk.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml
index 1b56584..f315e6f 100644
--- a/.github/workflows/sdk.yml
+++ b/.github/workflows/sdk.yml
@@ -24,7 +24,7 @@ jobs:
persist-credentials: false
# Pinned to a Ferrule commit; bump it to take generator fixes. Ferrule is not on PyPI yet, so
# the action installs it from its own checkout.
- - uses: shrijayan/ferrule/action@05d458d6594545d0f0647273b2df68fcc41143f9
+ - uses: shrijayan/ferrule/action@9bc0666bdd8fc7661fab11fe2d07703e28c0bdc1
with:
spec: openapi.json
config: stainless.yml
From b2655f32d8a625e944901c77a51f21c8f6f87dbf Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 18:51:59 +0530
Subject: [PATCH 38/41] Fix CI: bound the aiohttp extra, run the generated
suite on Linux, fuzz invalid UTF-8
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
.github/workflows/ci.yml | 9 ++++++---
docs/hooks/types_reference.py | 5 +----
fuzz/fuzz_sse.py | 7 +++++--
pyproject.toml | 2 +-
uv.lock | 2 +-
5 files changed, 14 insertions(+), 11 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7ffb923..09a0ab4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -55,12 +55,15 @@ jobs:
with:
node-version: 22
- run: uv sync --locked
+ # The generated tests run against the mock (pytest-asyncio), on Linux as the generated
+ # SDKs' own CI does; the hand-written ones in tests/custom use anyio and trio, so they run
+ # apart, on every OS.
- name: Start the mock server
+ if: runner.os == 'Linux'
shell: bash
run: ./scripts/mock --daemon
- # The generated tests run against the mock (pytest-asyncio); the hand-written ones in
- # tests/custom use anyio and trio, so they run apart.
- - run: uv run pytest --cov --cov-report=term-missing
+ - if: runner.os == 'Linux'
+ run: uv run pytest --cov --cov-report=term-missing
- run: >-
uv run pytest --confcutdir=tests/custom -o asyncio_mode=strict -o addopts="-ra --strict-markers"
-m "not live" --cov --cov-append --cov-report=term-missing tests/custom
diff --git a/docs/hooks/types_reference.py b/docs/hooks/types_reference.py
index ccf713a..f80a567 100644
--- a/docs/hooks/types_reference.py
+++ b/docs/hooks/types_reference.py
@@ -38,10 +38,7 @@ def _public_names() -> list[str]:
"""The models and aliases ``opencode.types`` re-exports (``from .x import X as X``)."""
tree = ast.parse(TYPES_SRC.read_text())
names = {
- alias.asname or alias.name
- for node in tree.body
- if isinstance(node, ast.ImportFrom)
- for alias in node.names
+ alias.asname or alias.name for node in tree.body if isinstance(node, ast.ImportFrom) for alias in node.names
}
return sorted(n for n in names if not n.startswith("_") and n[0].isupper())
diff --git a/fuzz/fuzz_sse.py b/fuzz/fuzz_sse.py
index 5790658..2e396b6 100644
--- a/fuzz/fuzz_sse.py
+++ b/fuzz/fuzz_sse.py
@@ -14,8 +14,11 @@
def test_one_input(data: bytes) -> None:
"""Feed arbitrary bytes through the SSE decoder, as the response stream would."""
chunks = [data[i : i + 7] for i in range(0, len(data), 7)]
- for _ in SSEDecoder().iter_bytes(iter(chunks)):
- pass
+ try:
+ for _ in SSEDecoder().iter_bytes(iter(chunks)):
+ pass
+ except UnicodeDecodeError:
+ pass # the decoder rejects bytes that are not UTF-8, as the event stream must be
if __name__ == "__main__":
diff --git a/pyproject.toml b/pyproject.toml
index 51204a0..81092c4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -46,7 +46,7 @@ pty = [
"websockets>=14,<17",
]
aiohttp = [
- "aiohttp",
+ "aiohttp>=3.10,<4",
"httpx-aiohttp>=0.1.9,<0.3",
]
diff --git a/uv.lock b/uv.lock
index 29f2d6f..29300f7 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1586,7 +1586,7 @@ docs = [
[package.metadata]
requires-dist = [
- { name = "aiohttp", marker = "extra == 'aiohttp'" },
+ { name = "aiohttp", marker = "extra == 'aiohttp'", specifier = ">=3.10,<4" },
{ name = "anyio", specifier = ">=4.4,<5" },
{ name = "distro", specifier = ">=1.7,<2" },
{ name = "httpx", specifier = ">=0.27,<1" },
From 8e89ef4982d59595b0e05c201fd3a552d7cf23f7 Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Mon, 26 Oct 2026 22:12:56 +0530
Subject: [PATCH 39/41] Run the spec scripts without typing_extensions
The CI step runs them with --no-project, where only the standard library is installed.
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
scripts/opencode/config_models.py | 3 +--
scripts/opencode/contract_to_openapi.py | 3 +--
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/scripts/opencode/config_models.py b/scripts/opencode/config_models.py
index 04a5d0e..bbeb87d 100644
--- a/scripts/opencode/config_models.py
+++ b/scripts/opencode/config_models.py
@@ -22,9 +22,8 @@
import re
import json
import argparse
-from typing import Any, cast
+from typing import Any, TypeAlias, cast
from pathlib import Path
-from typing_extensions import TypeAlias
Json: TypeAlias = dict[str, Any]
diff --git a/scripts/opencode/contract_to_openapi.py b/scripts/opencode/contract_to_openapi.py
index 8b112c0..1412755 100644
--- a/scripts/opencode/contract_to_openapi.py
+++ b/scripts/opencode/contract_to_openapi.py
@@ -23,9 +23,8 @@
import sys
import json
import argparse
-from typing import Any, cast
+from typing import Any, TypeAlias, cast
from pathlib import Path
-from typing_extensions import TypeAlias
Json: TypeAlias = dict[str, Any]
From 6f9eb48314ba535d494ee668674cfaa58f8a3edf Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Tue, 27 Oct 2026 00:20:09 +0530
Subject: [PATCH 40/41] Type-check with mypy on Python 3.10
mypy took the interpreter's version, so CI on Python 3.12 checked a branch of the runtime that pyright, set to 3.10, never sees.
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
pyproject.toml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/pyproject.toml b/pyproject.toml
index 81092c4..bd86262 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -150,6 +150,8 @@ reportUnknownParameterType = false
reportUnusedFunction = false
[tool.mypy]
+# Checked on the lowest supported Python, as pyright is (pythonVersion).
+python_version = "3.10"
pretty = true
show_error_codes = true
From a1432fac142a4aba5a297340d81828a6a33d2aaa Mon Sep 17 00:00:00 2001
From: Shrijayan <81805145+shrijayan@users.noreply.github.com>
Date: Tue, 27 Oct 2026 01:10:42 +0530
Subject: [PATCH 41/41] Make retry delays zero in the custom tests instead of
patching anyio.sleep
anyio before 4.12 implements lowlevel.checkpoint as sleep(0), so the global patch made the async event-feed poll spin forever under --resolution lowest-direct.
Signed-off-by: Shrijayan <81805145+shrijayan@users.noreply.github.com>
---
tests/custom/conftest.py | 13 ++++++-------
tests/custom/test_events.py | 4 ++--
2 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/tests/custom/conftest.py b/tests/custom/conftest.py
index 840d9dc..ca59a61 100644
--- a/tests/custom/conftest.py
+++ b/tests/custom/conftest.py
@@ -6,6 +6,7 @@
import pytest
from opencode import Opencode, AsyncOpencode
+from opencode._base_client import BaseClient
BASE_URL = "http://opencode.test"
PASSWORD = "test-password"
@@ -36,11 +37,9 @@ async def async_client(mock_api: respx.MockRouter) -> AsyncIterator[AsyncOpencod
@pytest.fixture(autouse=True)
def _no_retry_sleep(monkeypatch: pytest.MonkeyPatch) -> None:
- """Retries back off with real sleeps; skip them so tests stay fast."""
+ """Retries back off with real sleeps; make the delay zero so tests stay fast.
- async def instant(_: float) -> None:
- return None
-
- monkeypatch.setattr("opencode._base_client.time.sleep", lambda _: None)
- monkeypatch.setattr("opencode._base_client.anyio.sleep", instant)
- monkeypatch.setattr("opencode._base_client.asyncio.sleep", instant)
+ Patching ``anyio.sleep`` itself would also patch ``anyio.lowlevel.checkpoint`` on anyio
+ releases that implement it as ``sleep(0)``, and a task polling with it would never yield.
+ """
+ monkeypatch.setattr(BaseClient, "_calculate_retry_timeout", lambda *_args, **_kwargs: 0.0)
diff --git a/tests/custom/test_events.py b/tests/custom/test_events.py
index c82a53e..e587c4d 100644
--- a/tests/custom/test_events.py
+++ b/tests/custom/test_events.py
@@ -103,7 +103,7 @@ def wait_open(self, count: int) -> SyncFeed:
class AsyncFeed:
- """The async counterpart of ``SyncFeed``. It polls, since ``anyio.sleep`` is patched in tests."""
+ """The async counterpart of ``SyncFeed``. It polls with ``anyio.lowlevel.checkpoint``."""
def __init__(self) -> None:
self.chunks: collections.deque[bytes] = collections.deque()
@@ -304,7 +304,7 @@ def anyio_backend(request: pytest.FixtureRequest) -> str:
async def _wait_feed(server: Server, count: int) -> AsyncFeed:
with anyio.fail_after(5):
while len(server.feeds) < count:
- await anyio.lowlevel.checkpoint() # anyio.sleep is patched to return at once
+ await anyio.lowlevel.checkpoint()
feed = server.feeds[count - 1]
assert isinstance(feed, AsyncFeed)
return feed